PowerMachine commited on
Commit
b433ff3
·
verified ·
1 Parent(s): df03b79

KHTST v23 — auditoria independente integrada: correções válidas aplicadas com provas (doc 35, Teos 35.1-35.14) e claims refutadas documentadas (doc 35 §11). PCGrad PROJETA (fator=dot/n2, g=gm+(ga-fator*gm) — 0 violações I1/I2/I3 em 100.083 pares; o antigo reescalava e violava a não-piora em 25,3%); RoPE com OFFSET de cache (desloc absoluto propagado — score de decode varia com t; era constante 2,756113); tronco 5:1 REAL (n_camadas=6, validação do campo que constrói o tronco; L=3 reprovado); checkpoint ATÔMICO (tmp+fsync+replace+fsync-dir, hash obrigatória, antigos por último); IPC lê a capacidade do HEADER (divergência=ValueError, fallback+Cython); dedup texto+entrada+saida+midia (29,5%->8,7%) + NFC; filtro P9 sem vazamento eval->treino (9 benchmarks pulados, hendrycks em train, Fable train); acumulação sem viés na retomada; hub com buffer + fechar(); mecanismo árvore VIVO (A-23, y+0*y_a=y bit a bit); erratas doc 26/28 (Sigma 1/tau diverge, T_tau=168,76, dH/dT=Var/T3, h^2/12 densidade contínua); auditoria HARDENED em auditoria_independente/ (4 scripts com assert+exit, 4/4 verdes); corpus real fresco 1612 registros com P9 (MATH-500 PULADA); A/B pareado 48 passos + grad peso_arvore vivo medido; suíte v23 35/35, regressão v1-v22 zero, verificar_tudo 49✓

Browse files
Files changed (40) hide show
  1. .gitattributes +1 -0
  2. PLANO_KHMAMBAJEPA.md +21 -0
  3. README.md +60 -0
  4. RELATORIO_KHTST.md +46 -0
  5. auditoria_independente/CORRECAO_A01_PCGrad.md +86 -0
  6. auditoria_independente/EXECUTAVEL.md +281 -0
  7. auditoria_independente/PLANO_MELHORIAS_KHTST.md +557 -0
  8. auditoria_independente/README.md +30 -0
  9. auditoria_independente/modelo_ram.py +282 -0
  10. auditoria_independente/verif_cinza_lipschitz.py +144 -0
  11. auditoria_independente/verif_pcgrad_invariantes.py +135 -0
  12. auditoria_independente/verif_pcgrad_rope.py +127 -0
  13. configs/base.json +1 -1
  14. docs/matematica/26-gsq-rco-stiefel-riemanniano.md +17 -6
  15. docs/matematica/28-torchao-autoregulacao-mcjepa.md +17 -2
  16. docs/matematica/35-auditoria-independente-pcgrad-rope-eratas.md +322 -0
  17. graficos/v23/v23_evidencias.png +3 -0
  18. scripts/02_coletar_corpus.py +8 -13
  19. scripts/04_treinar.py +3 -0
  20. scripts/36_teste_v23.py +389 -0
  21. scripts/37_graficos_v23.py +163 -0
  22. scripts/38_publicar_v23.py +173 -0
  23. src/khtst/dados/checkpoints.py +46 -9
  24. src/khtst/dados/streaming.py +84 -16
  25. src/khtst/ipc/_fallback.py +13 -0
  26. src/khtst/ipc/khtst_ipc.c +0 -0
  27. src/khtst/ipc/khtst_ipc.cpython-312-x86_64-linux-gnu.so +2 -2
  28. src/khtst/ipc/khtst_ipc.pyx +11 -1
  29. src/khtst/mamba3/tronco.py +7 -2
  30. src/khtst/nucleo/modelo.py +10 -2
  31. src/khtst/nucleo/validacao.py +15 -1
  32. src/khtst/percepcao/atencao.py +59 -15
  33. src/khtst/percepcao/blocos.py +6 -4
  34. src/khtst/percepcao/vit_lra.py +10 -3
  35. src/khtst/servico/adhoc.py +5 -1
  36. src/khtst/telemetria/hub.py +31 -1
  37. src/khtst/treino/cirurgia_grad.py +19 -2
  38. src/khtst/treino/treinador.py +31 -3
  39. src/khtst/treino/treinador_estado.py +7 -0
  40. tests/conftest.py +37 -0
.gitattributes CHANGED
@@ -52,3 +52,4 @@ src/khtst/ipc/khtst_ipc.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merg
52
  graficos/v19/v19_evidencias.png filter=lfs diff=lfs merge=lfs -text
53
  src/khtst/acelerado/mamba3hot.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
54
  graficos/v22/v22_evidencias.png filter=lfs diff=lfs merge=lfs -text
 
 
52
  graficos/v19/v19_evidencias.png filter=lfs diff=lfs merge=lfs -text
53
  src/khtst/acelerado/mamba3hot.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
54
  graficos/v22/v22_evidencias.png filter=lfs diff=lfs merge=lfs -text
55
+ graficos/v23/v23_evidencias.png filter=lfs diff=lfs merge=lfs -text
PLANO_KHMAMBAJEPA.md CHANGED
@@ -244,6 +244,27 @@ honestidade.
244
  atualizados TODA versão; redundâncias removidas.
245
  ---
246
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
  ## 6b''. Verificação v22 (resumo executável)
248
 
249
  - Suíte v22 NOVA: **46/46 verdes** (Teos 34.1–34.11 citados: neutralidade
 
244
  atualizados TODA versão; redundâncias removidas.
245
  ---
246
 
247
+ ## 6b'''. Verificação v23 (resumo executável)
248
+
249
+ - Suíte v23 NOVA: **35/35 verdes** (doc 35 citado: PCGrad I1/I2/I3 na
250
+ implementação REAL; RoPE bit a bit + translação + decode variável;
251
+ checkpoint atômico a–d; IPC divergência (fallback+Cython); dedup/NFC;
252
+ 5:1 no campo real; acumulação sem viés; buffer de telemetria; erratas
253
+ E-01..E-04).
254
+ - REGRESSÃO v1–v22 TODA VERDE (25/36/✓/37/32/48/44/21/✓/✓/28/27/40/—/✓/56
255
+ /46/34/38/40/35) + verificar_tudo **49✓/0✗**; 2 falhas de regressão
256
+ PRÉ-EXISTENTES corrigidas (v21 §5: produtores movidos ao mixin v22;
257
+ v1/v2: configs de teste fora do contrato 5:1).
258
+ - Auditoria independente (2026-10-17) INTEGRADA: 14 correções válidas
259
+ aplicadas com provas (doc 35, Teos 35.1–35.14); 5 claims refutadas
260
+ documentadas; scripts hardened em `auditoria_independente/` (4/4 verdes
261
+ com assert + exit code).
262
+ - Corpus real FRESCO com filtro P9 (`scripts/36_teste_v23.py --coletar`):
263
+ 1612 registros (3 tarefas), MATH-500 PULADA (benchmark fora do treino),
264
+ hendrycks em train; A/B pareado 48 passos (uma variável: semântica v≤22
265
+ ablation vs v23) + gradiente do peso_arvore VIVO medido no treino real;
266
+ figuras em `graficos/v23/` (script 37).
267
+
268
  ## 6b''. Verificação v22 (resumo executável)
269
 
270
  - Suíte v22 NOVA: **46/46 verdes** (Teos 34.1–34.11 citados: neutralidade
README.md CHANGED
@@ -100,6 +100,66 @@ gradiente**. Provas em `docs/matematica/27`.
100
  > --teto_horas 9.0 --continuar_ate_horas 8.0 --estados_hf`).
101
 
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  ## O que há de novo no v22 — FECHAMENTO 18/18 DA TABELA v17: OS 10 PRODUTORES RESTANTES COM MEDIÇÕES REAIS · APLIERS NOVOS (ABMO/CIAR/CRESCEMENTO/PESO_SOM) · TREINO REAL PAREADO (provas em `docs/matematica/34`, memória em `PLANO_KHMAMBAJEPA.md`)
104
 
105
  - **Estado do otimizador — 4 sinais (doc 34 §2, Teos 34.1–34.5)**:
 
100
  > --teto_horas 9.0 --continuar_ate_horas 8.0 --estados_hf`).
101
 
102
 
103
+ ## O que há de novo no v23 — AUDITORIA INDEPENDENTE INTEGRADA: PCGrad PROJETA (e não reescala) · RoPE COM OFFSET DE CACHE · TRONCO 5:1 REAL (n_camadas=6) · CHECKPOINT ATÔMICO · IPC LÊ O HEADER · SEM VAZAMENTO EVAL→TREINO · ERRATAS DE MATEMÁTICA (provas em `docs/matematica/35`, auditoria hardened em `auditoria_independente/`)
104
+
105
+ Uma auditoria independente (2026-10-17, espelho v22) auditou o projeto com
106
+ scripts numpy-puros; o v23 **re-verificou cada claim contra o código real**
107
+ (5 claims refutadas — §11 do doc 35), aplicou as correções válidas com PROVAS
108
+ (Teos 35.1–35.14) e transformou os scripts da auditoria em rede de regressão
109
+ com `assert` + exit code:
110
+
111
+ - **PCGrad correto (A-01, Teo 35.1)** — `fator = dot/n2` com
112
+ `g = gm + (ga − fator·gm)` (Yu et al., 2020, Alg. 2): projeta g_aux no
113
+ complemento ortogonal. O código antigo REESCALAVA (25,3% dos conflitos
114
+ violavam a não-piora; pior caso 1,73×) e a "correção" intermediária do
115
+ auditor DOBRAVA o conflito (⟨g_aux′,g_main⟩ = 2·dot). Medido: 0 violações
116
+ de I1/I2/I3 na implementação real (398 pares torch + 100.083 numpy).
117
+ - **RoPE com offset de cache (A-03, Teo 35.8)** — `aplicar_rope(q,k,desloc)`
118
+ com posição absoluta propagada por gerar/SessaoKHTST: o score de decode
119
+ VARIA com t (era CONSTANTE 2,756113 para t=1,2,3,10 — o modelo não
120
+ distinguia posição 1 de 100). Prefill desloc=0 é v≤22 bit a bit.
121
+ - **Tronco 5:1 de verdade (A-04, Teo 35.5)** — `modelo.n_camadas = 6`
122
+ (5 Mamba + 1 NoPE exato) e a validação checa o campo que CONSTRÓI o tronco
123
+ (antes checava config morta; L=3 entregue tinha ZERO NoPE — reprovado).
124
+ - **Checkpoint atômico (I-03, Teo 35.3)** — tmp + fsync + os.replace +
125
+ fsync do diretório; hash SHA-256 OBRIGATÓRIA (meta ausente ⟹ rejeitado);
126
+ antigos apagados SÓ depois do novo par completo (crash ⟹ estado anterior
127
+ carregável).
128
+ - **IPC lê a capacidade do header (I-01, Teo 35.4)** — anexar com size
129
+ divergente do produtor levanta ValueError imediato (fallback E Cython;
130
+ antes: 0 frames ou corrupção CRC32 em silêncio). Header 256B compatível.
131
+ - **Dedup + NFC (T-02/T-08, Teos 35.6/35.7)** — chave
132
+ texto+entrada+saída+mídia única nos 3 consumidores (descarte real cai de
133
+ 29,5% para 8,7%); NFC idempotente na ingestão (BPE treinado em NFC; NFD
134
+ custava 1,198× tokens).
135
+ - **Sem vazamento eval→treino (T-03, Teo 35.14)** — 9 fontes de benchmark
136
+ (MMMU/MMMU-Pro/OCRBench/COCO-Caption/weikaih-eval/ASR×3/MATH-500 — todas
137
+ só-validation/test, verificado no datasets-server) marcadas
138
+ `papel:"avaliacao"` e PULADAS na coleta de treino; hendrycks migrou para o
139
+ split train real; válvula explícita `KHTST_INCLUIR_AVALIACAO=1`.
140
+ - **A-23 (Teo 35.13)** — o mecanismo ÁRVORE estava INERTE desde a v8
141
+ (curto-circuito `peso≠0` matava o gradiente do peso nascido 0): em treino
142
+ y_a é sempre computado e `y + 0·y_a = y` bit a bit (nascimento neutro
143
+ preservado); gradiente vivo medido no treino real (−14,16 no passo 48).
144
+ - **Robustez (A-05/A-19/A-22)** — acumulação de gradiente sem viés na
145
+ retomada (divide pelo tamanho REAL do grupo); telemetria com buffer
146
+ (64 linhas/2 s + `fechar()`; era ~20 open/close por passo); `avaliar()`
147
+ vazio agora ALERTA (não some em silêncio).
148
+ - **Erratas de matemática (E-01/E-02/E-03/E-04)** — Σ1/τ DIVERGE (removido
149
+ "< ∞" do doc 26); T_τ = 600/ln 35 ≈ 168,76 (não 308,1 — e o τ(600)=0,300
150
+ medido confirma); dH/dT = Var_p(z)/T³ (não /T²); h²/12 exige densidade
151
+ contínua (átomos na grade ⟹ 0).
152
+ - **Provas do que a auditoria alegou e NÃO sobreviveu (doc 35 §11)** —
153
+ E-05 (L_bal float), I-05 (peso_moe órfão), A-21 (conv sem guarda) e o
154
+ número 0,1502 do E-04: refutados contra o código v22/23 com arquivo:linha.
155
+ - **Auditoria hardened (P15)** — os 4 scripts numpy-puros agora em
156
+ `auditoria_independente/` com assert + exit code (antes só printavam);
157
+ suíte v23 nova: **35/35 verdes**; regressão v1–v22 toda verde +
158
+ `verificar_tudo` 49✓/0✗; A/B pareado real 48 passos (corpus FRESCO de
159
+ 1612 registros reais coletado com o filtro P9 — MATH-500 PULADA,
160
+ leitura honesta no doc 35 §14).
161
+
162
+
163
  ## O que há de novo no v22 — FECHAMENTO 18/18 DA TABELA v17: OS 10 PRODUTORES RESTANTES COM MEDIÇÕES REAIS · APLIERS NOVOS (ABMO/CIAR/CRESCEMENTO/PESO_SOM) · TREINO REAL PAREADO (provas em `docs/matematica/34`, memória em `PLANO_KHMAMBAJEPA.md`)
164
 
165
  - **Estado do otimizador — 4 sinais (doc 34 §2, Teos 34.1–34.5)**:
RELATORIO_KHTST.md CHANGED
@@ -1269,3 +1269,49 @@ expansão/poda SOM zeradas na fase A (honesto); custo dos acumuladores
1269
  não decompõe as causas dos ~2% de s/passo; o produtor τ_qk da v21 NÃO
1270
  estava morto (a v22 generaliza o filtro e aumenta a frescura — não é
1271
  correção de bug).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1269
  não decompõe as causas dos ~2% de s/passo; o produtor τ_qk da v21 NÃO
1270
  estava morto (a v22 generaliza o filtro e aumenta a frescura — não é
1271
  correção de bug).
1272
+
1273
+ ## 22. v23 — Auditoria independente integrada: correções válidas aplicadas com provas, claims refutadas documentadas, corpus sem vazamento
1274
+
1275
+ **Fonte:** auditoria externa de 2026-10-17 (7 arquivos: PLANO_MELHORIAS_KHTST.md,
1276
+ EXECUTAVEL.md, CORRECAO_A01_PCGrad.md, modelo_ram.py, verif_pcgrad_invariantes.py,
1277
+ verif_pcgrad_rope.py, verif_cinza_lipschitz.py) sobre o espelho v22. O v23
1278
+ re-verificou CADA claim contra o código real antes de agir — 5 claims caíram
1279
+ (§11 do doc 35), o resto foi corrigido com provas (doc 35, Teos 35.1–35.14).
1280
+
1281
+ **Correções aplicadas (arquivo:linha no commit):**
1282
+ | Achado | Correção v23 | Prova |
1283
+ |---|---|---|
1284
+ | A-01 PCGrad reescala | `fator=dot/n2`, `g=gm+(ga−fator·gm)` (cirurgia_grad.py) | Teo 35.1; suíte v23 §1: 0 violações I1/I2/I3 (398 pares torch; 100.083 numpy) |
1285
+ | A-03 RoPE sem offset | `desloc` em aplicar_rope + propagação (modelo.gerar, SessaoKHTST, MoA, blocos) | Teo 35.8; suíte v23 §2: prefill bit a bit, translação exata, decode VARIA com t |
1286
+ | A-04 tronco 3 camadas (0 NoPE) | `n_camadas=6` + validação do campo REAL (validacao.py) | Teo 35.5; suíte v23 §6: L=3 reprovado, L=6 ⟹ 5:1 |
1287
+ | I-03 checkpoint não-atômico | tmp+fsync+os.replace+fsync(dir); hash obrigatória; apagar por último | Teo 35.3; suíte v23 §3 (a–d) |
1288
+ | I-01 IPC header nunca lido | anexar valida capacidade (fallback + Cython; .so recompilado) | Teo 35.4; suíte v23 §4 |
1289
+ | T-02 dedup 29,5% | `_chave_dedup` (texto+entrada+saída+mídia) única nos 3 consumidores | Teo 35.7; descarte 29,5%→8,7% |
1290
+ | T-03/T-01 splits de avaliação + Fable "lite" | `papel:"avaliacao"` em 9 fontes benchmark (probe datasets-server), hendrycks→train, Fable→train; filtro P9 com evento PULADA | Teo 35.14; coleta real: MATH-500 PULADA, 1612 registros |
1291
+ | T-08 NFD | NFC na ingestão (idempotente — Teo 35.6) | suíte v23 §5 |
1292
+ | A-05 acumulação com viés na retomada | divide pelo tamanho REAL do grupo (`_n_acc_atual`) | Teo 35.9; suíte v23 §7 |
1293
+ | A-19 hub open/close por linha | buffer 64 linhas/2 s + `fechar()` no scripts/04 | Teo 35.10; suíte v23 §8 |
1294
+ | A-22 avaliar() vazio silencioso | alerta explícito no hub/stdout | — |
1295
+ | A-23 árvore INERTE desde a v8 | em treino y_a sempre computado; `y+0·y_a = y` bit a bit | Teo 35.13; grad medido no treino real (−14,16) |
1296
+ | P16 docstring Lipschitz falso | vit_lra.py cita o bound real do Teo 27.7(ii-a) | verif_cinza_lipschitz.py (razão ≤1; 2δ violado 45× no pixel escuro) |
1297
+ | E-01/E-02 (doc 26) | erratas: Σ1/τ DIVERGE; T_τ=600/ln 35≈168,76 (τ(600)=0,300 confirma) | Teo 35.12; suíte v23 §9a-b |
1298
+ | E-03/E-04 (doc 28) | erratas: dH/dT=Var/T³ (FD 0,985); h²/12 exige densidade contínua | Teo 35.11; suíte v23 §9c-d |
1299
+ | P15 scripts sem assert | auditoria hardened em `auditoria_independente/` (assert + exit code, 4/4 verdes) | — |
1300
+ | P12 suíte torch-dependente | tests/conftest.py: coleta vira skip com mensagem clara sem torch | — |
1301
+
1302
+ **Claims da auditoria REFUTADAS contra o código real (não aplicadas):**
1303
+ E-05 (`perda_lb` é tensor com gradiente — treinador.py:606), I-05
1304
+ (`razao_moe_ce` produzido no mixin v22 — tabela 18/18 real), A-21 (único
1305
+ call site da conv profunda é guardado por `_hot is not None` — kernel.py:199),
1306
+ número 0,1502 do E-04 (internamente inconsistente: E[Δ²] ≤ max|Δ|² = 6,8e-4;
1307
+ o ponto qualitativo entrou na errata com o contra-exemplo EXATO on-grid).
1308
+
1309
+ **Teste real (scripts/36_teste_v23.py + 37_graficos_v23.py):** corpus FRESCO
1310
+ 1612 registros (3 tarefas, P9 auditado — MATH-500 PULADA, hendrycks em train);
1311
+ A/B pareado exato 48 passos com UMA variável (semântica v≤22 ablation via
1312
+ monkeypatch vs v23 limpo): baseline 9,671→7,974 (1,213×) vs v23 9,719→8,497
1313
+ (1,144×), ΔCE +0,134 — leitura honesta idêntica ao precedente v22: os fixes
1314
+ são de CORREÇÃO com invariantes provados; corrida curta não mede o valor
1315
+ (que é estrutural: não-piora garantida, posição relativa restaurada, árvore
1316
+ viva, treino sem vazamento). Regressão: 21 suítes verdes (v1–v23) +
1317
+ verificar_tudo 49✓/0✗. Gráfico: graficos/v23/v23_evidencias.png.
auditoria_independente/CORRECAO_A01_PCGrad.md ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CORREÇÃO A-01 — PCGrad projeta o gradiente ERRADO
2
+
3
+ > **APLICADA NO v23 (2026-10-18).** O patch abaixo está em produção em
4
+ > `src/khtst/treino/cirurgia_grad.py` (`fator = dot / n2_main` com
5
+ > `g = gm + (ga - fator * gm)`) — provas no doc 35 §1 (Teo 35.1: invariantes
6
+ > I1/I2/I3 com 0 violações em 100.083 pares numpy + 398 pares torch na
7
+ > implementação REAL), regressão em `auditoria_independente/verif_pcgrad_
8
+ > invariantes.py` e `tests/test_khtst_v23.py` §1. O teste de aceite esperado
9
+ > no fim deste arquivo é agora o comportamento observado.
10
+ #
11
+ # Arquivo original: src/khtst/treino/cirurgia_grad.py
12
+ # Linhas originais: 90-101 (bloco `fator` e composição `g`)
13
+ #
14
+ # ─────────────────────────────────────────────────────────────────────────────
15
+ # O PROBLEMA (o bug é real; a correção anterior dos agentes tinha sinal trocado)
16
+ # ─────────────────────────────────────────────────────────────────────────────
17
+ # Código original (treino/cirurgia_grad.py:90-101):
18
+ # projetado = dot < 0.0 and n2_main > eps
19
+ # fator = -dot / n2_main if projetado else 0.0
20
+ # ...
21
+ # g = gm + (fator * ga if projetado else ga)
22
+ #
23
+ # Isso NÃO é projeção: é uma REESCALA de g_aux por um escalar. A garantia do
24
+ # PCGrad (Yu et al., 2020) não é atendida.
25
+ #
26
+ # ─────────────────────────────────────────────────────────────────────────────
27
+ # A CORREÇÃO
28
+ # ─────────────────────────────────────────────────────────────────────────────
29
+ # PCGrad Alg. 2 (2 tarefas), com dot = <g_main, g_aux> e n2 = ||g_main||²:
30
+ # se dot < 0: g_aux' = g_aux - (dot / n2) * g_main
31
+ # total = g_main + g_aux'
32
+ #
33
+ # ATENÇÃO AO SINAL: o multiplicador de g_main é (dot / n2) — que é NEGATIVO
34
+ # quando há conflito. A variante `fator = -dot/n2` combinada com
35
+ # `g = gm + (ga - fator*gm)` (correção anterior dos agentes) computa
36
+ # g_aux' = g_aux + (dot/n2)*g_main, ou seja:
37
+ # <g_aux', g_main> = dot + dot = 2*dot < 0
38
+ # → o conflito é DOBRADO, não removido. Medido: 100.083/100.083 pares
39
+ # violam a ortogonalidade (ver /workspace/khtst_audit/minha_verificacao/
40
+ # verif_pcgrad_invariantes.py).
41
+ #
42
+ # ─────────────────────────────────────────────────────────────────────────────
43
+ # INVARIANTES QUE A VERSÃO CORRETA SATISFAZ (com prova)
44
+ # ─────────────────────────────────────────────────────────────────────────────
45
+ # I1 <g_aux', g_main> = <g_aux,g_main> - (dot/n2)*<g_main,g_main>
46
+ # = dot - dot = 0 ✔
47
+ # I2 ||g_aux'||² = ||g_aux||² - (dot/n2)²·||g_main||²
48
+ # = ||g_aux||² - dot²/n2 ≤ ||g_aux||² ✔ (contração)
49
+ # I3 <g_total, g_main> = <g_main,g_main> + <g_aux',g_main> = n2 ≥ 0 ✔
50
+ # <g_total, g_aux'> = <g_main,g_aux'> + ||g_aux'||² = ||g_aux'||² ≥ 0 ✔
51
+ #
52
+ # Custo: uma subtração vetorial por parâmetro — O(P), desprezível ao lado
53
+ # do backward. Nenhuma mudança de hiperparâmetro.
54
+ #
55
+ # ─────────────────────────────────────────────────────────────────────────────
56
+ # PATCH (aplicar sobre as linhas 90-101 do arquivo original)
57
+ # ─────────────────────────────────────────────────────────────────────────────
58
+ # projetado = dot < 0.0 and n2_main > eps
59
+ # with torch.no_grad():
60
+ # - fator = -dot / n2_main if projetado else 0.0
61
+ # + # Yu et al. (2020), Alg. 2: o multiplicador de g_main e (dot/n2),
62
+ # + # que e NEGATIVO quando ha conflito. Usar -dot/n2 aqui dobra o
63
+ # + # conflito (<g_aux', g_main> = 2*dot < 0) em vez de remove-lo.
64
+ # + fator = dot / n2_main if projetado else 0.0
65
+ # for p, gm, ga in zip(params, g_main, g_aux):
66
+ # if gm is None and ga is None:
67
+ # continue
68
+ # if gm is None:
69
+ # g = ga
70
+ # elif ga is None:
71
+ # g = gm
72
+ # else:
73
+ # - g = gm + (fator * ga if projetado else ga)
74
+ # + # projeta g_aux no complemento ortogonal de g_main
75
+ # + g = gm + (ga - fator * gm if projetado else ga)
76
+ # p.grad = g.detach() if p.grad is None else p.grad + g.detach()
77
+ # return {"pcgrad/dot": dot, "pcgrad/projetado": float(projetado),
78
+ # "pcgrad/fator": fator}
79
+ #
80
+ # ─────────────────────────────────────────────────────────────────────────────
81
+ # TESTE DE ACEITE (sem torch: substitua torch.no_grad por contexto nulo)
82
+ # ─────────────────────────────────────────────────────────────────────────────
83
+ # python3 /workspace/khtst_audit/minha_verificacao/verif_pcgrad_invariantes.py
84
+ # esperado: variante 'correto' viola I1=0, I2=0, I3=0
85
+ # variante 'atual' viola I1 em ~100% dos pares
86
+ # variante 'agente' viola I1 em 100% dos pares (pior que o original)
auditoria_independente/EXECUTAVEL.md ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # EXECUTAVEL — como rodar a auditoria e as correções do KHTST
2
+
3
+ > **ATUALIZAÇÃO v23 (2026-10-18).** As correções P1–P6, P9–P11, P15, P16 e
4
+ > A-05/A-19/A-22/A-23 ESTÃO APLICADAS no repositório oficial (com provas no
5
+ > doc 35). As seções abaixo permanecem para (a) reproduzir a auditoria em
6
+ > um ambiente limpo e (b) re-verificar tudo do zero. O caminho de re-execução
7
+ > ATUAL é: clonar o repo oficial e rodar
8
+ > `python3 auditoria_independente/verif_pcgrad_invariantes.py` (os 4 scripts
9
+ > hardened, com assert + exit code), `python3 tests/test_khtst_v23.py`
10
+ > (suíte 35/35, exige torch) e as demais suítes v1–v22 — os "correcoes/" do
11
+ > auditório NÃO são mais necessários (e a correção do PCGrad do auditor
12
+ > tinha sinal trocado — ver §6 deste guia, mantido como registro histórico).
13
+
14
+ Guia ponta a ponta, sem contexto prévio. Todo comando foi executado neste ambiente
15
+ (Python 3.11.2, 2 cores, ~3 GB RAM, sem GPU, sem torch).
16
+
17
+ ---
18
+
19
+ ## 0. Ambiente
20
+
21
+ ```bash
22
+ # pip: o índice padrão do sandbox está vazio — use o PyPI explícito
23
+ pip install --break-system-packages --index-url https://pypi.org/simple \
24
+ pyarrow tokenizers "datasets>=3.0" requests
25
+
26
+ # opcional: pyyaml para inspecionar o plano
27
+ pip install --break-system-packages --index-url https://pypi.org/simple pyyaml
28
+ ```
29
+
30
+ > **Não instale torch aqui.** O sandbox tem 3 GB de RAM e nenhum torch é necessário
31
+ > para nenhuma das verificações: toda a matemática é reimplementada em numpy puro.
32
+
33
+ ---
34
+
35
+ ## 1. Espelho do repositório (nunca modificar)
36
+
37
+ ```bash
38
+ cd /workspace/khtst_audit
39
+ GIT_SSL_NO_VERIFY=1 GIT_LFS_SKIP_SMUDGE=1 git clone --depth 1 \
40
+ https://huggingface.co/PowerMachine/khtst-multimodal-ptbr repo
41
+
42
+ # integridade: tem que sair VAZIO
43
+ git -C repo status --porcelain
44
+ # (se aparecer __pycache__, adicione ao .git/info/exclude:
45
+ # printf '__pycache__/\n*.pyc\n' >> repo/.git/info/exclude )
46
+ ```
47
+
48
+ As extensões `.so` do repositório são **cpython-312 x86-64**: não importam em Python
49
+ 3.11. Todo caminho testado adiante é o Python puro (fallback), e isso é proposital —
50
+ é assim que se descobre módulo sem fallback.
51
+
52
+ ---
53
+
54
+ ## 2. Reproduzir as verificações dos agentes
55
+
56
+ ```bash
57
+ cd /workspace/khtst_audit/verificacao
58
+
59
+ # scripts de prova matemática (v01..v09)
60
+ for f in v0*.py; do
61
+ b=$(basename "$f" .py)
62
+ timeout 300 python3 "$f" > "/tmp/RUN_$b.txt" 2>&1
63
+ echo "$b rc=$? $(diff -q "out_$b.txt" "/tmp/RUN_$b.txt" >/dev/null && echo IDENTICO || echo DIVERGE)"
64
+ done
65
+ ```
66
+
67
+ Resultado esperado hoje (numpy 2.4.6):
68
+
69
+ ```
70
+ v01 rc=0 DIVERGE # só os últimos dígitos de float
71
+ v02 rc=0 DIVERGE
72
+ v03 rc=0 IDENTICO
73
+ v04 rc=0 IDENTICO
74
+ v05 rc=1 DIVERGE # CRASH: numpy.AxisError na linha 109 <-- achado meu
75
+ v06 rc=0 DIVERGE
76
+ v07 rc=0 DIVERGE
77
+ v08 rc=1 DIVERGE # CRASH: np.trapz removido no numpy>=2 <-- achado meu
78
+ v09 rc=0 IDENTICO
79
+ ```
80
+
81
+ **Dois scripts quebram.** Correção (P15):
82
+
83
+ ```bash
84
+ # v08: numpy>=2 renomeou trapz -> trapezoid
85
+ sed -i 's/np\.trapz/np.trapezoid/g' v08_docs_00_25.py
86
+ # v05: ent_rel espera um VETOR de logits, mas recebe um escalar
87
+ # (linha 109: e = np.exp(lv - lv) -> e = np.array([0.0,0.0,0.0]))
88
+ ```
89
+
90
+ ---
91
+
92
+ ## 3. Minhas verificações (as que sustentam o plano)
93
+
94
+ ```bash
95
+ cd /workspace/khtst_audit/minha_verificacao
96
+
97
+ python3 verif_pcgrad_invariantes.py # PCGrad: 3 variantes x 3 invariantes
98
+ python3 verif_pcgrad_rope.py # PCGrad: garantia "nenhuma tarefa piora" + RoPE
99
+ python3 verif_cinza_lipschitz.py # Lipschitz do detector de cinza
100
+ python3 modelo_ram.py # modelo de RAM, c* do checkpointing, tabela por RAM
101
+ ```
102
+
103
+ Saídas esperadas (trechos que decidem o plano):
104
+
105
+ ```
106
+ variante pares viola I1 viola I2 viola I3
107
+ atual 100,083 100,070 0 49,948
108
+ agente 100,083 100,083 100,081 791 <-- a correção do agente DOBRA o conflito
109
+ correto 100,083 0 0 0
110
+
111
+ t=1 <q_rot,k_rot> codigo=+2.756113 com offset=+2.889119 DIVERGE
112
+ t=10 <q_rot,k_rot> codigo=+2.756113 com offset=+1.970870 DIVERGE
113
+ (o score é CONSTANTE: o modelo não distingue t=1 de t=10)
114
+
115
+ max (derivada real)/(bound do doc) = 0.999989 -> DOMINADO (teorema VÁLIDO)
116
+ cinza 0.01 + delta 0.001: |Δs| = 0.090090 | 2δ = 0.002000 -> VIOLADO
117
+ (o que é falso é o DOCSTRING do código, não o teorema)
118
+ ```
119
+
120
+ ---
121
+
122
+ ## 4. Reproduzir a coleta de dados real
123
+
124
+ ```bash
125
+ cd /workspace/khtst_audit/verificacao
126
+ timeout 1500 python3 t4_p3b_corpus_real.py # baixa e coleta das 39 fontes
127
+ python3 -c "
128
+ import json;d=json.load(open('saidas/t4_p3b_corpus_real.json'))
129
+ print('registros:',d['n_total'],'bytes:',d['bytes']);print('tarefas:',d['tarefas'])"
130
+ ```
131
+
132
+ Saída esperada (varia 1.752–1.992 conforme as dependências instaladas: sem `requests`
133
+ a fonte TTS some):
134
+
135
+ ```
136
+ corpus: .../corpus_real_mini.jsonl n=1812 3.1 MB
137
+ tarefas: {'pontuacao':60,'instrucao':132,'lm':120,'noticia':120,'imagem_caption':240,
138
+ 'vqa':180,'ocr':60,'tts':60,'classificacao':120,'traducao':120,'robo':240,
139
+ 'matematica':360}
140
+ ```
141
+
142
+ Sinal de que o split inexistente está lá (fonte [24]):
143
+
144
+ ```
145
+ [24] MoreThought/Fable-5.1-Max-Reasoning-Filtered-5000x n= 0 lidos= 0 3.0s []
146
+ ```
147
+
148
+ E o erro real da API (probe B do t4):
149
+
150
+ ```
151
+ split_lite: "ERRO ValueError: BuilderConfig 'lite' not found. Available: ['default']"
152
+ ```
153
+
154
+ ### 4.1 Reproduzir o custo do dedup (a correção P4)
155
+
156
+ ```bash
157
+ cd /workspace/khtst_audit/verificacao && python3 - <<'EOF'
158
+ import json, collections
159
+ regs=[json.loads(l) for l in open('saidas/corpus_real_mini.jsonl',encoding='utf-8')]
160
+ k_streaming = lambda r: r.get("texto") or r.get("saida") or r.get("entrada") or ""
161
+ k_coletar = lambda r: (r.get("texto") or ((r.get("entrada") or "")+"\u241f"+(r.get("saida") or "")))
162
+ for nome, fn in (("streaming.py:1013 (atual)", k_streaming), ("02_coletar:66", k_coletar)):
163
+ mantidos=len(collections.Counter(fn(r) for r in regs))
164
+ print(f"{nome:26s} descartados={len(regs)-mantidos:4d} ({100*(len(regs)-mantidos)/len(regs):.1f}%)")
165
+ EOF
166
+ ```
167
+
168
+ Esperado no corpus de 1.812 registros: **534 (29,5%)** pela chave atual e
169
+ **394 (21,7%)** pela de `02_coletar` — contra **158 (8,7%)** com a chave corrigida
170
+ (texto+entrada+saida+sha1 da mídia).
171
+
172
+ ---
173
+
174
+ ## 5. Reproduzir o teste do IPC (capacidade nunca lida)
175
+
176
+ ```bash
177
+ cd /workspace/khtst_audit/repo/src && python3 - <<'EOF'
178
+ import sys; sys.path.insert(0,'.')
179
+ from khtst.ipc._fallback import KHTSTCanal as C
180
+ size = 4*1024*1024
181
+ canal = C(b"khtst_teste", size)
182
+ print("cap no header:", canal._ler_u64(8))
183
+ canal.enviar_frame(1, b"PING"+bytes(range(256))*4, seq=0)
184
+ print("roundtrip ok:", any(p==b"PING"+bytes(range(256))*4 for _,p in canal.receber_frames()))
185
+ outro = C.anexar(canal.fd, size//2) # consumidor com size DIFERENTE
186
+ print("cap recomputada:", outro.capacidade(), "| header continua:", canal._ler_u64(8))
187
+ print("frames lidos:", len(outro.receber_frames())) # <-- 0, SEM ERRO
188
+ EOF
189
+ ```
190
+
191
+ Esperado: `frames lidos: 0` — **perda silenciosa**, sem exceção. É o P6.
192
+
193
+ ---
194
+
195
+ ## 6. Aplicar as correções (todas já prontas em `correcoes/`)
196
+
197
+ ```bash
198
+ cd /workspace/khtst_audit
199
+
200
+ # 1) conferir que todas compilam
201
+ for f in $(find correcoes -name '*.py'); do python3 -m py_compile "$f" || echo "FALHOU $f"; done
202
+
203
+ # 2) a do PCGrad tem ERRO DE SINAL — NÃO usar a do agente;
204
+ # usar a versão em minha_verificacao/CORRECAO_A01_PCGrad.md:
205
+ # fator = dot / n2_main (e não -dot / n2_main)
206
+ # g = gm + (ga - fator * gm if projetado else ga)
207
+
208
+ # 3) nunca copiar por cima do espelho do HF: o destino é a sua cópia local
209
+ # cp -r repo meu_khtst && rsync -a --ignore-existing correcoes/src/ meu_khtst/src/
210
+ ```
211
+
212
+ Correções prontas (36 arquivos, todos compilam): `correcoes/src/khtst/**`,
213
+ `correcoes/bench/**` (benchmark de RSS e modelo de memória),
214
+ `correcoes/streaming_corrigido.py`, `correcoes/tokenizador_corrigido.py`,
215
+ `correcoes/treinador_corrigido.py`, `correcoes/base_corrigido.json`,
216
+ `correcoes/migrar_constantes.py` (fonte única de verdade das constantes),
217
+ `correcoes/docs/matematica/CORRECOES_MATEMATICAS.md`.
218
+
219
+ ---
220
+
221
+ ## 7. Camada de backend (P13) — para hardware chinês
222
+
223
+ ```bash
224
+ # 1) criar a camada
225
+ # src/khtst/nucleo/backend.py (código no §7.3 do PLANO_MELHORIAS_KHTST.md)
226
+
227
+ # 2) substituir as 19 ocorrências de torch.cuda.* :
228
+ grep -rn --include=*.py --include=*.pyx -E '\.cuda\(|device=.cuda.|torch\.cuda\.' src scripts
229
+ # substituir por: from khtst.nucleo.backend import backend_disponivel, device, vazia_cache
230
+
231
+ # 3) instalar o runtime do backend NA MÁQUINA ALVO (nunca no sandbox)
232
+ # Ascend: pip install torch-npu==<versão compatível> \
233
+ # --extra-index-url https://repo.huaweicloud.com/ascend/repos/pypi
234
+ # Moore: torch_musa (https://github.com/MooreThreads/torch_musa) + MUSA SDK
235
+ # Hygon: torch_hcu (vem com o DTK) + FlagTree HCU
236
+ # Iluvatar:torch_xiu + flagtree==<ver>+iluvatar3.x
237
+ # Kunlun: torch_xmlir (Python 3.8) + FlagTree XPU
238
+ # Cambricon: torch_mlu (compila contra PyTorch patcheado)
239
+
240
+ # 4) RECOMPILAR as extensões Cython NA MÁQUINA ALVO
241
+ cd meu_khtst/src/khtst/acelerado && python3 setup.py build_ext --inplace
242
+ cd ../ipc && python3 setup.py build_ext --inplace
243
+ # os .so do repo são cpython-312 e não carregam em outros ambientes
244
+
245
+ # 5) desligar a quantização int8 em backends sem kernel CUDA
246
+ # (quanta/torchao_quant.py depende de tinygemm sm>=8.0 / int8pack)
247
+ ```
248
+
249
+ Verificação do backend:
250
+
251
+ ```python
252
+ from khtst.nucleo.backend import backend_disponivel, device
253
+ print("backend:", backend_disponivel(), "| device:", device())
254
+ ```
255
+
256
+ ---
257
+
258
+ ## 8. Critérios de aceite por fase (resumo)
259
+
260
+ | Fase | Aceite verificável |
261
+ |---|---|
262
+ | 0 | `verif_pcgrad_invariantes.py` → 0 violações; `verif_cinza_lipschitz.py` → docstring coerente; `v05`/`v08` rodam sem crash; `validacao.py` reprova `n_camadas=3` |
263
+ | 1 | dedup ≤ 10% de descarte; `anexar()` com `size` divergente levanta erro; 0 registros de split de avaliação no corpus; `pytest` coleta sem torch |
264
+ | 2 | RSS do `insere_paralela` cai ~2× em T=65.536; `c*` reproduz a memória prevista |
265
+ | 3 | `backend_disponivel()` correto em cada máquina; `.so` importável no Python local; perplexidade dentro de 1% da CPU |
266
+ | 4 | todo número do README com `arquivo:linha` + comando; "18/18" → "17/18" |
267
+
268
+ ---
269
+
270
+ ## 9. O que NÃO dá para rodar neste sandbox (e por quê)
271
+
272
+ | Item | Motivo |
273
+ |---|---|
274
+ | treino real do modelo | sem torch e sem GPU; 3 GB de RAM |
275
+ | `.so` do repositório | cpython-312 x86-64; o sandbox é Python 3.11 |
276
+ | medição de throughput | sem GPU e com 2 cores |
277
+ | port para NPU/DCU | precisa do SDK do fabricante na máquina alvo |
278
+
279
+ Tudo o mais (provas matemáticas, dedup, IPC, RoPE, PCGrad, modelo de RAM,
280
+ recolha de dados reais) **roda e foi rodado** — os scripts estão em
281
+ `/workspace/khtst_audit/minha_verificacao/` e em `/workspace/khtst_audit/verificacao/`.
auditoria_independente/PLANO_MELHORIAS_KHTST.md ADDED
@@ -0,0 +1,557 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # KHTST — Resultados consolidados, confirmação independente e aprimoramentos
2
+
3
+ > **RESOLUÇÃO v23 (2026-10-18) — LEIA PRIMEIRO.** Este documento é a
4
+ > auditoria independente de 2026-10-17. O projeto **re-verificou cada claim
5
+ > contra o código real** e aplicou as correções válidas com provas no doc 35
6
+ > (`docs/matematica/35-auditoria-independente-pcgrad-rope-eratas.md`):
7
+ > **APLICADAS**: P1 (PCGrad), P2 (RoPE offset), P3 (tronco 5:1, L=6),
8
+ > P4 (dedup+mídia, 29,5%→8,7%), P5 (checkpoint atômico), P6 (IPC header),
9
+ > P9 (filtro eval→treino, 9 fontes de benchmark), P10 (Fable→train,
10
+ > hendrycks→train), P11 (NFC), P12-lite (conftest torch-free), P15 (os 4
11
+ > scripts de auditoria agora em `auditoria_independente/` com assert+exit),
12
+ > P16 (docstring Lipschitz) + A-05 (acumulação sem viés na retomada),
13
+ > A-19 (hub com buffer), A-22 (avaliar vazio alerta), A-23 (mecanismo árvore
14
+ > INERTE desde a v8 — corrigido com nascimento neutro preservado) e as
15
+ > erratas E-01..E-04 nos docs 26/28. **REFUTADAS** (não aplicadas, prova em
16
+ > doc 35 §11): E-05 (L_bal float — perda_lb É tensor), I-05 (peso_moe órfão
17
+ > — razao_moe_ce produzido na v22), A-21 (conv sem guarda — call site é
18
+ > guardado), número 0,1502 do E-04 (internamente inconsistente). P7/P8/P13
19
+ > (buffer rolante da janela, checkpointing c*, backend NPU/DCU) permanecem
20
+ > como ROTEIRO (não eram bugs). Suíte v23: 35/35; regressão v1–v22 verde;
21
+ > verificar_tudo 49✓/0✗; corpus real fresco 1612 registros sem vazamento.
22
+
23
+ **Escopo:** auditoria do projeto `PowerMachine/khtst-multimodal-ptbr` (HuggingFace), com
24
+ foco em (a) falhas de lógica, (b) validade das provas matemáticas, (c) consumo de RAM,
25
+ (d) integração entre pacotes, (e) portabilidade para hardware chinês.
26
+
27
+ **Data:** 2026-10-17 · **Espelho do repositório:** `/workspace/khtst_audit/repo`
28
+ (clone local de `https://huggingface.co/PowerMachine/khtst-multimodal-ptbr`, **não
29
+ modificado** — `git status --porcelain` vazio, confirmado ao final de cada etapa).
30
+
31
+ > **Regra deste documento:** cada número aparece com uma marca de procedência.
32
+ > `MEDIDO` = executei e colei a saída. `AGENTE` = executei um agente, com a saída
33
+ > preservada em `verificacao/`. `TEÓRICO` = consequência de um teorema, sem execução.
34
+ > Claim **refutada** não volta como válida em nenhum lugar deste documento.
35
+
36
+ ---
37
+
38
+ ## 1. Sumário executivo — o estado real do projeto
39
+
40
+ O KHTST é um trabalho sério: 185 arquivos Python + 4 Cython + 35 documentos de prova
41
+ (~38.933 linhas), 24 subpacotes, 36 correções prontas que compilam, e uma suíte de
42
+ verificação numérica honesta. O que o projeto **não** é, hoje, é o que o README
43
+ promete. Os desacoplamentos reais entre código, documentação e métricas são:
44
+
45
+ 1. **O tronco "híbrido 5:1" não existe na configuração entregue.** `configs/base.json`
46
+ tem `modelo.n_camadas = 3`; o padrão NoPE é `(i+1) % 6 == 0` → **zero camadas
47
+ NoPE**. A validação que deveria garantir o contrato 5:1 checa
48
+ `modelo.mamba3.n_camadas` (6) — um campo que **não** constrói o tronco. O único
49
+ lugar que corrige isso é `scripts/29_ipc_treino_modelo.py:61`.
50
+ 2. **O PCGrad não projeta gradiente nenhum** — ele reescala `g_aux` por um escalar
51
+ (`cirurgia_grad.py:90-101`). Medido: em **25,3%** dos pares em conflito o
52
+ gradiente resultante ainda tem produto interno negativo com `g_aux`, e o pior
53
+ caso é **1,73× pior** que o conflito original.
54
+ 3. **O RoPE reinicia a posição a cada passo de decodificação** (`atencao.py:39-57`,
55
+ chamadas em 100 e 179, sem offset). Medido: o score de atenção de um token novo
56
+ contra o cache é **idêntico** (2,756113) nos passos t=1, t=2, t=3 e t=10 — o
57
+ modelo não distingue "token na posição 1" de "token na posição 100".
58
+ 4. **O IPC nunca lê a capacidade que ele mesmo grava no cabeçalho.** Medido com o
59
+ código real: se consumidor e produtor discordarem do `size` da região, o
60
+ consumidor lê **zero frames, sem erro**.
61
+ 5. **O checkpoint grava em cima do destino** (sem `os.replace`/`fsync`), grava o
62
+ `meta.json` **depois** do tensor (e a validação de hash é pulada se o meta
63
+ faltar) e **apaga os checkpoints antigos depois** de escrever o novo — um crash
64
+ deixa o novo truncado e o antigo já destruído.
65
+ 6. **O dedup do corpus descarta 29,5% dos registros reais** porque a chave é
66
+ `texto or saida or entrada` (só a saída, para registros seq2seq). Medido no meu
67
+ corpus real de 1.812 registros. A chave correta recupera **+24%** de dados.
68
+ 7. **Dez das 39 fontes do plano são splits de avaliação** e entram no mesmo
69
+ corpus de treino. E a fonte `MoreThought/Fable-5.1` pede um split `"lite"` que
70
+ **não existe** → entra com 0 registros.
71
+ 8. **Os números de capa do README descrevem outro modelo**: "237 camadas, 78,8 MB →
72
+ 10,1 MB int8 (3,92×)" e "RAM pico 3.066 MB" não batem com o config entregue
73
+ (3 camadas) nem com a física de RSS (Python+numpy = 29,5 MB; o doc 28 declara
74
+ 78,8 MB de pesos). Um pico absoluto de 3,066 MB é numericamente impossível.
75
+
76
+ **A parte boa:** as provas de Stiefel/RCO, a cota `ℓ_max ≤ d/√d_h`, o decaimento
77
+ analítico de τ e a conservação do SOM estão corretas onde foram re-derivadas; o
78
+ `while` de checkpointing, o `torch.compile` e a camada de quantização têm
79
+ arquitetura sadia; e a suíte de verificação por numpy que os próprios agentes
80
+ escreveram é a melhor parte do repositório.
81
+
82
+ **O veredito:** o KHTST tem a forma de um sistema maduro e a aparência de um. O caminho
83
+ crítico não é otimização de RAM — é **corrigir três bugs de matemática que
84
+ invalidam resultados publicados** (PCGrad, RoPE, tronco 5:1) e **parar de publicar
85
+ métricas que o config entregue não produz**.
86
+
87
+ ---
88
+
89
+ ## 2. Método: o que os agentes fizeram e o que eu refiz sozinho
90
+
91
+ ### 2.1 O que os agentes entregaram (preservado em disco)
92
+
93
+ | Artefato | Conteúdo | Estado |
94
+ |---|---|---|
95
+ | `relatorios/t1_auditoria_estrutural.md` | 23 achados (4 críticos, 6 altos, 8 médios, 5 baixos) em 24 subpacotes | entregue; **parcialmente refutado** na verificação |
96
+ | `relatorios/t2_provas_matematicas.md` | 80 claims de 35 documentos + 9 scripts numpy | entregue; claim 79 e 17/19 refutados |
97
+ | `relatorios/t4_testes_datasets_reais.md` | 37 datasets inventariados, 39 fontes em streaming real, corpus de ~1.9k registros | entregue; 2 números exagerados (ver §4) |
98
+ | `relatorios/t5_integracao.md` | grafo de imports (125 módulos), IPC, 18/18 da tabela v17 | entregue; 2 números corrigidos |
99
+ | `relatorios/verify_t1.md` | conferência adversarial da t1 | entregue (não foi re-conferido por mim em tudo) |
100
+ | `relatorios/verify_t2.md` | conferência adversarial da t2 (minha segunda rodada) | entregue |
101
+ | `relatorios/verify_t4.md`, `verify_t5.md` | conferências adversariais | entregues |
102
+ | `correcoes/` | 36 arquivos corrigidos (compilam) | entregue; **1 tem erro de sinal (PCGrad)** |
103
+ | `verificacao/` | 19 scripts de prova + saídas | entregue; 2 scripts quebrados (ver §4) |
104
+
105
+ ### 2.2 O que ficou incompleto
106
+
107
+ - **`t3` (memória/RAM + hardware chinês) nunca foi entregue** — o trabalhador foi
108
+ interrompido no limite de 30 min. **Eu produzi essa parte** (§6 e §7), a partir do
109
+ config e do código reais.
110
+ - **Não existem as revisões de segunda camada** (`verify_t1_revisado`,
111
+ `verify_t45_revisado`): ninguém reconferiu as conferências. Eu conferi as
112
+ conclusões queustinham o plano.
113
+ - **Não existia plano final nem guia de execução** antes deste documento.
114
+
115
+ ### 2.3 O que eu refiz sozinho (sem os agentes)
116
+
117
+ | Verificação minha | Script | Resultado |
118
+ |---|---|---|
119
+ | PCGrad: invariantes corretos × 3 variantes | `minha_verificacao/verif_pcgrad_invariantes.py` | original viola I1 em 100.070/100.083; correção dos agentes viola em 100.083/100.083; a minha em 0 |
120
+ | PCGrad: garantia "nenhuma tarefa piora" | `minha_verificacao/verif_pcgrad_rope.py` | 25,3% de violação; pior caso 1,73× |
121
+ | RoPE: invariância de posição na decodificação | `minha_verificacao/verif_pcgrad_rope.py` | score constante (2,756113) para t=1,2,3,10 |
122
+ | Lipschitz do detector de cinza | `minha_verificacao/verif_cinza_lipschitz.py` | teorema do doc **válido** (razão 0,999989); docstring do código **falso** (viola 45×) |
123
+ | IPC: capacidade do header nunca lida | teste com o módulo real `khtst.ipc._fallback` | consumidor com `size` diferente lê 0 frames, sem erro |
124
+ | Dedup sobre corpus real | re-execução de `t4_p3b_corpus_real.py` + cálculo próprio | 1.812 registros; 29,5% descartados pela chave atual; 8,7% pela chave corrigida |
125
+ | NFD × tokens com BPE real | `tokenizers` sobre o corpus real | 0/1.500 textos em NFD; inflação real 1,198× (não 2,64×) |
126
+ | Split inexistente | re-execução da coleta real | `MoreThought/Fable-5.1` → 0 registros; erro `BuilderConfig 'lite' not found` |
127
+ | Modelo de RAM | `minha_verificacao/modelo_ram.py` | 5,04 M params; 20,1 MB fp32; estado 4× = 100,7 MB; gargalo = janela paralela |
128
+ | Acoplamento a CUDA | grep real no repo | 19 linhas em 9 arquivos; zero `autocast`/`nccl`/`triton`/`flash_attn`/`pin_memory` |
129
+ | Reprodução dos scripts dos agentes | re-execução de v01..v09 | v01/v02/v06/v07 diferem só nos últimos dígitos; **v05 e v08 quebram** |
130
+
131
+ ---
132
+
133
+ ## 3. Índice de achados (estado consolidado)
134
+
135
+ `CONFIRMADO-ME` = reproduzi eu mesmo, com saída executada.
136
+ `CONFIRMADO-AGENTE` = o achado se sustenta, mas a evidência é do agente.
137
+ `REFUTADO` = não vale;listado em §3.2 para transparência.
138
+
139
+ ### 3.1 Achados que valem
140
+
141
+ | ID | Origem | Local | Descrição | Sev | Estado |
142
+ |---|---|---|---|---|---|
143
+ | A-01 | t1 | `treino/cirurgia_grad.py:90-101` | PCGrad reescala `g_aux` em vez de projetar: a garantia "nenhuma tarefa piora" é violada em 25,3% dos conflitos | CRÍTICO | **CONFIRMADO-ME** (bug real; a *prova* do agente era fabricada) |
144
+ | A-01' | t1 | `correcoes/.../cirurgia_grad.py:66-81` | a correção proposta pelo agente usa `fator = -dot/n2` com `ga - fator*gm` → conflito **dobrado** (`⟨g_aux',g_main⟩ = 2·dot < 0`), viola I1 em 100% dos pares | CRÍTICO | **CONFIRMADO-ME** (pior que o original) |
145
+ | A-03 | t1 | `percepcao/atencao.py:39-57` | RoPE sem offset: na decodificação o token novo sempre recebe posição 0 | CRÍTICO | **CONFIRMADO-ME** |
146
+ | A-04 | t1 | `configs/base.json` + `nucleo/validacao.py:63-67` | tronco com 3 camadas → 0 NoPE; a validação do 5:1 checa outro campo | CRÍTICO | **CONFIRMADO-ME** |
147
+ | A-05 | t1 | `treino/treinador.py:709` | acumulação de gradiente corrompida na retomada | ALTO | CONFIRMADO-AGENTE |
148
+ | A-08/A-17 | t1 | `memoria/interna.py` | listas/dicts que crescem por iteração | ALTO | CONFIRMADO-AGENTE |
149
+ | A-11/A-12/A-15/A-16 | t1 | `quanta/gsq_rco.py` | robustez numérica da quantização | MÉDIO | CONFIRMADO-AGENTE |
150
+ | A-19 | t1 | `telemetria/hub.py:186-190` | ~20 `open()/close()` por passo (I/O-bound) | MÉDIO | CONFIRMADO-AGENTE |
151
+ | A-21 | t1 | `mamba3/kernel.py:158-170` | `_ConvCausalProfunda` chama o binário sem checar `_hot is not None` → `ImportError` em vez do fallback | MÉDIO | CONFIRMADO-AGENTE |
152
+ | A-22 | t1 | `treino/treinador.py:96-101` | com <2 registros, `avaliar()` devolve `{}` e o gate de fase some em silêncio | MÉDIO | CONFIRMADO-AGENTE |
153
+ | A-23 | t1 | `percepcao/atencao.py:299-306` | `peso_arvore` nasce em 0 e só é escrito quando o mecanismo já roda → mecanismo permanentemente inerte | MÉDIO | CONFIRMADO-AGENTE |
154
+ | A-20 | t1 | `quanta/gsq_rco.py:800,867` | `float64` onde `float32` bastaria — **ganho real ≤ 1,5 kB** (o próprio agente calibrou a expectativa) | BAIXO | CONFIRMADO-AGENTE |
155
+ | E-01 | t2 | `docs/matematica/26` §5-ii | `Σ 1/τ(t) < ∞` é **falso** (a série diverge); meu recálculo: T=1000 → 1.673,3; T=10⁶ → 1.999.673,2 | ALTO | **CONFIRMADO-ME** (casa dígito a dígito) |
156
+ | E-02 | t2 | `docs/matematica/26` §9 | `T_τ = 600/ln7 ≈ 308,1` com δ=0,05: o valor correto é **168,76** (= 600/ln 35) | MÉDIO | **CONFIRMADO-ME** |
157
+ | E-03 | t2 | `docs/matematica/28` Teo 28.8 | `dH/dT = Var_p(z)/T²` → o correto é **/T³** | ALTO | CONFIRMADO-AGENTE (verificado por diferença numérica FD/T³ = 1,00000 em 15/15) |
158
+ | E-04 | t2 | `docs/matematica/28` Teo 28.1 | `E‖Δ‖² = h²/12` vale para densidade **contínua**; com átomos na grade vale 0 | MÉDIO | CONFIRMADO-AGENTE (contra-exemplo explícito) |
159
+ | E-05 | t2 | `nucleo/modelo.py` (MoE) | `L_bal` entra como `float` → gradiente ≡ 0 | ALTO | CONFIRMADO-AGENTE |
160
+ | E-06 | t2 | `percepcao/moe.py` | convenção de τ divergente entre docs (1/4τ vs 1/2τ) | MÉDIO | CONFIRMADO-AGENTE |
161
+ | E-07 | t2 | README §v14 | "entropia do seletor 0,87" não tem poder discriminativo: varia 3e-4 para Δlogit=5 | MÉDIO | **CONFIRMADO-ME** (saída de `out_v05`: 0,6497→0,6500) |
162
+ | E-08 | t2 | README §v12 | "RAM pico 3.066 MB" é numericamente impossível como pico absoluto | MÉDIO | **CONFIRMADO-ME** (29,5 MB só com Python+numpy; 78,8 MB de pesos no doc 28) |
163
+ | E-09 | t2 | README/doc 28 | "237 camadas, 78,8 MB → 10,1 MB int8 (3,92×)" descreve um modelo que o config entregue não instancia (3 camadas) | ALTO | **CONFIRMADO-ME** |
164
+ | T-01 | t4 | `dados/streaming.py:896` | split `"lite"` inexistente → a fonte MoreThought/Fable entra com 0 registros | ALTO | **CONFIRMADO-ME** (erro real da API HF) |
165
+ | T-02 | t4 | `dados/streaming.py:1013` | chave de dedup `texto or saida or entrada` descarta 29,5% dos registros reais | ALTO | **CONFIRMADO-ME** (534/1.812) |
166
+ | T-03 | t4 | `dados/streaming.py` (PLANO) | 10 de 39 fontes são splits de avaliação dentro do corpus de treino | ALTO | CONFIRMADO-AGENTE (probe J: train 29, validation 2, test 6, td_path 1, lite 1) |
167
+ | T-04 | t4 | `dados/streaming.py` (PLANO) | `allenai/fetchman-data` (robô) e matemática fora da mistura `base.json` | MÉDIO | CONFIRMADO-AGENTE |
168
+ | T-05 | t4 | `dados/streaming.py` | `sfarrukhm/intel-image-classification` tem 1 classe só | MÉDIO | CONFIRMADO-AGENTE |
169
+ | T-06 | t4 | `dados/streaming.py` | suíte de testes do repo é 100% torch-dependente → **nenhum teste roda** sem torch | ALTO | **CONFIRMADO-AGENTE** (20/20 arquivos falham por import) |
170
+ | T-07 | t4 | `percepcao/vit_lra.py:201-203` | o **docstring do código** repete o bound Lipschitz "2δ" que o doc 27 diz ser falso | MÉDIO | **CONFIRMADO-ME** (viola em 45×; o *teorema* é válido) |
171
+ | T-08 | t4 | `dados/streaming.py` | sem normalização Unicode no ingestão | BAIXO (latente) | **CONFIRMADO-ME**: 0/1.500 textos reais em NFD; custo real 1,198× (não 2,64×) |
172
+ | I-01 | t5 | `ipc/_fallback.py:75` + `khtst_ipc.pyx:265` | capacidade gravada no header nunca lida; divergência de `size` = perda silenciosa | ALTO | **CONFIRMADO-ME** (0 frames lidos, sem erro) |
173
+ | I-02 | t5 | `quanta/gsq_rco.py:93-94` | `TAU_MIN_PADRAO`/`T_ALVO_PADRAO` não são o τ efetivo (0,25 vem de `ajustar_k_tau_min`, default 0,25) | ALTO | CONFIRMADO-AGENTE |
174
+ | I-03 | t5 | `dados/checkpoints.py:63-95` | escrita não-atômica, `meta` depois do tensor, limpeza do antigo depois da escrita | CRÍTICO | **CONFIRMADO-ME** |
175
+ | I-04 | t5 | `ipc` / `treino/estado_integral.py` | rótulo de esquema gravado e nunca lido | ALTO | CONFIRMADO-AGENTE |
176
+ | I-05 | t5 | tabela v17 | `peso_moe` órfão → a tabela é 17/18, não 18/18 | MÉDIO | CONFIRMADO-AGENTE |
177
+ | I-06 | t5 | `nucleo/guarda_runtime.py` | duas cópias literais de `rss_mb` | MÉDIO | CONFIRMADO-AGENTE |
178
+ | I-07 | t5 | `quanta/gsq_rco.py` | colisão de nome `erro_quantizacao` com semânticas incompatíveis | MÉDIO | CONFIRMADO-AGENTE |
179
+ | I-08 | t5 | 125 módulos / 167 arestas | grafo de imports **sem ciclos** — confirmado; mas a contagem de 167 arestas foi corrigida | BAIXO | CONFIRMADO-AGENTE (0 ciclos confirmado) |
180
+ | R-01 | t3 | **não entregue pelo agente** | modelo de RAM e matriz de hardware chinês | ALTO | **FEITO POR MIM** (§6, §7) |
181
+
182
+ ### 3.2 Achados REFUTADOS (não valem — não reaproveitar)
183
+
184
+ | ID | O que se alegava | Por que caiu |
185
+ |---|---|---|
186
+ | A-10 (núcleo) | "capacidade divergente entre a implementação Python e a Cython" | a função comparada é **código morto**; as duas implementações vivas concordam em todos os pontos testados. Restou só a guarda assimétrica `__init__` vs `anexar` — **BAIXO**, não ALTO |
187
+ | A-07 | "memória do SSM ~6 tokens" | linha errada (145→157) e a matemática estava invertida |
188
+ | A-01 (prova) | tabela de ângulos do PCGrad | foi produzida com o **sinal trocado** (`gm+(ga+fator·gm)`); o invariante declarado era matematicamente falso. O *bug* é real, a *prova* não era |
189
+ | A-08 | prova do crescimento de listas | prova internamente contraditória; linhas erradas |
190
+ | — | "cobertura 191/191 (100%) lidos" | é contagem de **arquivo**, não de leitura; a métrica honesta ("63 arquivos abertos") também é falsa (são 101) |
191
+ | — | "correções A-01 e A-03 são patches" | eram prosa comentada; `atencao.py` nem importava |
192
+ | t2 claim 79 | entropia "0,333 → 0,999" | o script morre na linha 109 **antes** de imprimir qualquer um desses números |
193
+ | t2 claims 17/19 | "PROVADO (código)" | eram fórmulas transcritas à mão; o repo não era importado nem lido |
194
+ | t4 A-06 | "NFD custa ×2,64 tokens" | o número real é **1,198×** (medido com o BPE real); e 0 de 1.500 textos reais estão em NFD — risco latente, não erro ativo |
195
+ | t4 probe I | "Lipschitz do detector de cinza FALSO" | o **teorema** é válido (razão 0,999989 ≤ 1); o que é falso é o docstring do **código** |
196
+
197
+ ---
198
+
199
+ ## 4. Provas matemáticas: o que sobrevive, o que cai
200
+
201
+ ### 4.1 Confirmadas por mim
202
+
203
+ **I1 — PCGrad: os invariantes corretos.** Com `dot = ⟨g_main,g_aux⟩` e `n2 = ‖g_main‖²`,
204
+ a versão correta (Yu et al., 2020, Alg. 2) é `g_aux' = g_aux − (dot/n2)·g_main`, e então:
205
+
206
+ ```
207
+ I1 ⟨g_aux', g_main⟩ = dot − (dot/n2)·n2 = 0 (ortogonalidade exata)
208
+ I2 ‖g_aux'‖² = ‖g_aux‖² − dot²/n2 ≤ ‖g_aux‖² (contração)
209
+ I3 ⟨g_total, g_main⟩ = n2 ≥ 0 e ⟨g_total, g_aux'⟩ = ‖g_aux'‖² ≥ 0
210
+ ```
211
+
212
+ Medido em 100.083 pares com conflito (`verif_pcgrad_invariantes.py`):
213
+
214
+ | variante | viola I1 | viola I2 | viola I3 |
215
+ |---|---|---|---|
216
+ | código atual (`gm + fator·ga`) | 100.070 | 0 | 49.948 |
217
+ | correção do agente (`gm + (ga − fator·gm)`, `fator=−dot/n2`) | 100.083 | 100.081 | 791 |
218
+ | **correção correta (`fator = dot/n2`)** | **0** | **0** | **0** |
219
+
220
+ Isto é uma correção real, não cosmética: a versão do agente **dobra** o conflito
221
+ (`⟨g_aux',g_main⟩ = 2·dot < 0`). Contra-exemplo simbólico: `gm=(1,0)`, `ga=(−1,0)`
222
+ → versão atual dá `g=(0,0)`; versão do agente dá `g=(−1,0)` (pior!); versão correta
223
+ dá `g=(1,0)` com componente auxiliar **nula**.
224
+
225
+ **I2 — RoPE exige referencial de posição compartilhado.** A propriedade do RoPE é
226
+ `⟨R_p q, R_j k⟩ = ⟨q, R_{j−p} k⟩`, o que exige que `q` e `k` compartilhem a
227
+ **mesma origem** de posições. No prefill, `q` e `k` vêm do mesmo tensor com
228
+ `pos = arange(T)` → a invariância translacional vale. Na decodificação, `q` e `k`
229
+ novos recebem `pos = arange(1) = [0]`, enquanto o cache guarda `k` rotacionado com
230
+ as posições **originais** → o offset relativo passa de `(t − j)` para `(0 − j)`.
231
+ Medido: `⟨q_rot, k_rot⟩ = 2,756113` para t = 1, 2, 3 e 10 — **constante**. A
232
+ correção é um parâmetro `offset` em `aplicar_rope`, alimentado pelo número de
233
+ tokens já presentes no cache.
234
+
235
+ **I3 — o Lipschitz do detector de cinza (doc 27, Teo 27.7(ii)(a)) está correto.**
236
+ Com `χ = (mx − mn)/(mx + ε)`:
237
+ ```
238
+ ∂χ/∂mx = (ε + mn)/(mx+ε)² , ∂χ/∂mn = −1/(mx+ε)
239
+ |∂χ/∂I| ≤ (ε+mn)/(mx+ε)² + 1/(mx+ε) = (mx + mn + 2ε)/(mx+ε)²
240
+ bound do doc: 2/(mx+ε) + χ/(mx+ε) = (2mx + 2ε + mx − mn)/(mx+ε)²
241
+ bound_doc − bound_real = 2(mx − mn)/(mx+ε)² ≥ 0 ⇒ o bound do doc DOMINA o real
242
+ ```
243
+ Medido: `max(derivada real / bound do doc) = 0,999989 ≤ 1`. O que é falso é o
244
+ **docstring do código** (`vit_lra.py:201-203`), que repete o bound ingênuo `2δ`:
245
+ pixel cinza 0,01 com um canal +0,001 → `|Δs| = 0,0901` = **45× o 2δ**.
246
+
247
+ **I4 — `Σ 1/τ(t)` diverge.** Com `τ(t) = τ_min + (τ_0−τ_min)·e^{−t/T_τ}` e
248
+ `τ_min > 0`, `1/τ(t) ≥ 1/τ_0 > 0` ⇒ a série não converge. Meus números:
249
+ `T=1000 → 1.673,3`; `T=10⁶ → 1.999.673,2`; `S/T → 1,9997`. Bate dígito a dígito
250
+ com o relatório.
251
+
252
+ **I5 — `T_τ` do doc 26 §9.** `T_τ = t_alvo / ln((τ_0−τ_min)/δ)`. Com `t_alvo=600`,
253
+ `τ_0=1,75`, `δ=0,05`: `600/ln(35) = 168,76`, não `600/ln7 = 308,34`.
254
+
255
+ ### 4.2 Refutadas com contra-exemplo
256
+
257
+ - **`E|Δ|² = h²/12` para "qualquer densidade"** — vale para densidade contínua
258
+ (erro ~1e-4 com 4·10⁶ amostras), mas uma densidade **discreta com átomos
259
+ exatamente na grade** (ex.: pesos em `k·s`) tem `Δ ≡ 0` e `E[Δ²] = 0` — violação
260
+ de 100%. E com *clipe* saturado (`normal(0,1)`): `E[Δ²] = 0,1502` contra
261
+ `h²/12 = 5,17e-06` — violação de 4 ordens de grandeza, muito mais forte que a
262
+ ressalva original. Consequência: `‖X̃−X‖_∞ ≤ s/2` **só vale enquanto o clipe não
263
+ satura** (em `normal(0,0,2)`, `max|Δ| = 0,026` = 6,6× o `s/2 = 0,0039`).
264
+ - **`dH/dT = Var_p(z)/T²`** — o correto é `/T³` (verificado por diferenças finitas:
265
+ `FD/(Var/T³) = 1,00000` em 15/15 combinações).
266
+
267
+ ### 4.3 Scripts de prova que não rodam (achado meu, que escapou a todos)
268
+
269
+ | script | problema | evidência |
270
+ |---|---|---|
271
+ | `v05_metricas_readme.py` | crasha na linha 109: `ent_rel(np.exp(lv−lv))` passa **escalar** onde a função soma no eixo 1 → `numpy.AxisError` | saída real em `verificacao/saidas/`; o relatório t2 publica os números da linha seguinte, que o script nunca imprime |
272
+ | `v08_docs_00_25.py` | usa `np.trapz`, **removido no numpy ≥ 2.0** (virou `np.trapezoid`) | `AttributeError: module 'numpy' has no attribute 'trapz'` com numpy 2.4.6 (o agente rodou com numpy 1.24 e não viu) |
273
+
274
+ Recomendação: trocar comparações textuais por `assert`/exit code (hoje os 9 scripts
275
+ não têm **nenhum** `assert`, então nenhum check aborta) e fixar `numpy<2` ou migrar
276
+ `np.trapz`.
277
+
278
+ ---
279
+
280
+ ## 5. Achados de integração que sobreviveram
281
+
282
+ - **Grafo de imports: 125 módulos, 0 ciclos** — confirmado. A contagem de 167 arestas
283
+ foi corrigida na verificação (a dependência existe, o número não).
284
+ - **0 ciclos de import** é um resultado **forte** e raro: significa que a extração
285
+ pode ser feita por ordem topológica sem reordenar nada.
286
+ - **Contratos quebrados**: 4/4 confirmados com `arquivo:linha` nos dois lados
287
+ (incluindo `erro_quantizacao` homônimo com semânticas incompatíveis).
288
+ - **Tabela v17**: 3 de 4 itens confirmados, 1 confirmado com citação errada;
289
+ `peso_moe` é órfão → **17/18, não 18/18**.
290
+ - **Duas rotinas de dedup divergem** (`streaming.py:1013` vs `02_coletar_corpus.py:66`)
291
+ — 140 registros afetados na medição do agente; a minha medição (1.812 registros)
292
+ dá 534 vs 394 descartados, com 158 em comum.
293
+
294
+ ---
295
+
296
+ ## 6. Modelo de RAM (feito por mim, sobre o config e o código reais)
297
+
298
+ `minha_verificacao/modelo_ram.py` → saída completa em `minha_verificacao/saida_modelo_ram.txt`.
299
+
300
+ ### 6.1 Parâmetros e estado (contagem analítica)
301
+
302
+ | item | valor | fp32 |
303
+ |---|---|---|
304
+ | embedding (16384 × 192) | 3.145.728 | 12,58 MB |
305
+ | por camada do tronco (atenção + Mamba3 + FFN + micro) | 629.760 | 2,52 MB |
306
+ | tronco (L = 3) | 1.889.280 | 7,56 MB |
307
+ | **total** | **5.035.008** | **20,14 MB** |
308
+ | pesos + grads + momento Muon + (m,v) do AdamW | 4×P | **100,70 MB** |
309
+
310
+ ### 6.2 O gargalo real: a janela paralela 1M
311
+
312
+ `memoria/janela_1m.py:184` materializa `segs = oculto.new_zeros(M, w_seg, d)` com
313
+ `w_seg = 256` e passo `s = 128`. Como `W/s = 2`, cada token aparece em **2 janelas**
314
+ → o tensor materializado tem **2× o tamanho do chunk**:
315
+
316
+ | chunk T | `segs` | buffers fixos (z + grade SOM) | total |
317
+ |---|---|---|---|
318
+ | 512 | 0,59 MB | 3,11 MB | 3,69 MB |
319
+ | 4.096 | 6,09 MB | 3,11 MB | 9,21 MB |
320
+ | 16.384 | 24,97 MB | 3,11 MB | 28,11 MB |
321
+ | 65.536 | 100,47 MB | 3,11 MB | 103,70 MB |
322
+
323
+ **Prova da redundância:** com `M ≈ T/s` janelas de largura `W`, o total de cópias é
324
+ `M·W ≈ (T/s)·W = T·(W/s) = 2T`. Uma implementação com **buffer rolante** (duas
325
+ janelas sobrepostas) usa `O(W)` em vez de `O(T)` → **2× de RAM a menos no termo
326
+ dominante**, com o mesmo resultado (a ordem de atualização da grade SOM é preservada
327
+ por construí-la em ordem).
328
+
329
+ ### 6.3 Ponto ótimo do checkpointing (prova)
330
+
331
+ Com ativação de `k` tensores `(B,c,H)` por segmento de comprimento `c`:
332
+ ```
333
+ M(c) = B·S·H·4·(k_min) + (S/c)·B·c·H·4·k ≈ B·H·4·(S + k·S/c + c·k)
334
+ Minimizando em c: dM/dc = 0 ⇒ c* = √(S/k)
335
+ M* = B·H·4·2·√(k·S) (ganho √(k·S) sobre o sem-checkpoint)
336
+ Tempo: T(c) = (fwd + bwd) + 1 fwd de recomputação = 4 unidades, INDEPENDENTE de c
337
+ ⇒ overhead constante de +33%, e o ótimo de memória não custa tempo extra.
338
+ ```
339
+ Medido: S=4.096 → 113,25 MB sem ckpt → **1,02 MB** com `c* = 18,5` (**110,9×**);
340
+ S=65.536 → 1.811,94 MB → **4,09 MB** com `c* = 73,9` (**443×**).
341
+
342
+ ### 6.4 Tabela de orçamento por RAM (treino, fp32, lote 6)
343
+
344
+ Termos: estado 4×P (100,7 MB) + KV (56,6 MB) + scores da atenção + ativação + janela.
345
+
346
+ | RAM | S viável (limitado pela janela paralela) | S viável (limitado pelos scores) | `c*` do ckpt |
347
+ |---|---|---|---|
348
+ | 3 GB | **2.645** | 42.160 | 15 |
349
+ | 8 GB | **7.196** | 114.694 | 24 |
350
+ | 16 GB | **14.478** | 230.749 | 35 |
351
+ | 24 GB | **21.759** | 346.804 | 43 |
352
+ | 48 GB | **43.605** | 694.970 | 60 |
353
+
354
+ *Nota:* a variante **GLOBAL** de atenção custa `O(B·H·S²)`: 19,33 GB só nos scores com
355
+ S=8.192, e 19,8 **TB** com S=262.144. A variant **JANELA** (raio 128) custa
356
+ `O(B·H·S·257)`: 0,152 GB em S=2.048 e 19,4 GB em S=262.144. Ou seja, **a partir de
357
+ ~20 GB de RAM a atenção global é aritmeticamente impossível** — o modo janela não é
358
+ uma otimização, é a única opção.
359
+
360
+ ---
361
+
362
+ ## 7. Portabilidade para hardware chinês (verificada em fontes oficiais)
363
+
364
+ **Notícia boa:** o acoplamento do KHTST a CUDA é **pequeno** — 19 linhas em 9 arquivos,
365
+ com `torch.cuda.is_available()` usado como *sonda de capacidade* (não requisito),
366
+ zero `autocast`, zero `nccl`, zero `triton`, zero `flash_attn`, zero `pin_memory`.
367
+ `scripts/24, 31, 33` já checam `torch.xpu.is_available()`. O núcleo é agnóstico.
368
+
369
+ | item | arquivo:linha | tipo | o que fazer |
370
+ |---|---|---|---|
371
+ | sonda de backend | `quanta/gsq_rco.py:210,212`; `quanta/quantizacao.py:268,285,317,326,331`; `quanta/torchao_quant.py:47,56`; `geracao/difusao.py:121`; `acelerado/rco_gsq.pyx:71,73`; `telemetria/gestor_memoria.py:106,107` | `is_available()` + `get_device_name` | trocar por um `backend_disponivel()` único |
372
+ | seleção de device | `scripts/12_gsq_rco_hibrido.py:70,75` | `device = "cuda" if ...` + `.cuda()` | usar o seletor |
373
+ | higiene de memória | `telemetria/gestor_memoria.py:107` | `torch.cuda.empty_cache()` | cache do backend + `gc.collect()` |
374
+
375
+ ### 7.1 Matriz de backends (nomes de pacote conferidos em fonte oficial)
376
+
377
+ | Backend | Pacote / API | Fonte (aberta) | Situação para o KHTST |
378
+ |---|---|---|---|
379
+ | **Ascend NPU** | `torch-npu` (PyPI, 2.14.0 em out/2026); `torch.npu.is_available()`, `torch.npu.device_count()`; índice extra `https://repo.huaweicloud.com/ascend/repos/pypi` | https://pypi.org/project/torch-npu/ | **recomendado 1º**: wheels prontas, `torch.cuda` → `torch.npu` |
380
+ | **Moore Threads** | `torch_musa` (repo aberto, MUSA SDK + muDNN) | https://docs.mthreads.com/en/torchmusa/torchmusa-doc-online/ · https://github.com/MooreThreads/torch_musa | viável; exige SDK MUSA instalado |
381
+ | **Hygon DCU** | `torch_hcu` (vem com o DTK, **não está no PyPI**) + FlagTree HCU; DTK validado em Python 3.8–3.12 | https://github.com/flagos-ai/FlagTensor/blob/main/setup.sh | viável, mas exige SDK no host |
382
+ | **Iluvatar CoreX** | `torch_xiu` + `flagtree==<ver>+iluvatar3.x` (BI-V150) | mesma URL | viável; wheels cp310/cp312 |
383
+ | **Kunlunxin XPU** | `torch_xmlir` + FlagTree XPU; **SDK só para Python 3.8**; runtime em `/opt/xre/so` | mesma URL | **restrição de Python** — exige venv 3.8 |
384
+ | **Cambricon MLU** | `torch_mlu` (compila contra PyTorch patcheado; NeuWare SDK) | https://github.com/Cambricon/torch_mlu | mais custoso: compila do zero, `torch.device("mlu")` |
385
+ | Intel XPU (referência) | `torch.xpu` nativo | https://docs.pytorch.org/docs/stable/xpu.html | já é o caminho "não-CUDA" mais maduro |
386
+
387
+ ### 7.2 O que realmente trava o port (e não é o device)
388
+
389
+ 1. **As 5 extensões Cython** (`acelerado/{mamba3hot,mc_jepa,nucleos,rco_gsq}.pyx`,
390
+ `ipc/khtst_ipc.pyx`). Os `.so` do repositório são **cpython-312 x86-64** — não
391
+ carregam em Python 3.11 nem em builds do usuário. É preciso **recompilar por
392
+ plataforma** (`python setup.py build_ext --inplace` na pasta do pacote, com o
393
+ toolchain Cython + compilador da plataforma).
394
+ 2. **A quantização torchao int8** (`quanta/torchao_quant.py`) depende de kernels
395
+ CUDA (`tinygemm` sm≥8.0) e de `int8pack` na CPU. Em NPU/DCU não há equivalente
396
+ pronto: o caminho seguro é **desligar a quantização int8** e usar bf16/fp16,
397
+ que todos esses backends suportam nativamente.
398
+ 3. **A ordem da `.so` do HF não é alterável** — como o usuário pediu, nada foi
399
+ modificado no repositório do HuggingFace. O port é entregue como camada de
400
+ abstração + instruções.
401
+
402
+ ### 7.3 A camada de abstração mínima (proposta)
403
+
404
+ ```python
405
+ # src/khtst/nucleo/backend.py (NOVO — não mexe em mais nada)
406
+ _BACKENDS = ("npu", "mlu", "hcu", "xiu", "xmlir", "xpu", "cuda", "cpu")
407
+
408
+ def backend_disponivel() -> str:
409
+ """Retorna o primeiro backend realmente utilizavel, em ordem de preferencia.
410
+ NUNCA chama .cuda() — o device e resolvido por nome de backend, e todo
411
+ .cuda() do repo deve virar torch.device(DEVICE)."""
412
+ for nome, mod, attr in (("npu", "torch_npu", "npu"), ("mlu", "torch_mlu", "mlu"),
413
+ ("hcu", "torch_hcu", "hcu"), ("xiu", "torch_xiu", "xiu"),
414
+ ("xmlir", "torch_xmlir", "xmlir"), ("xpu", None, "xpu")):
415
+ try:
416
+ if mod is not None:
417
+ __import__(mod)
418
+ return nome if getattr(torch, attr).is_available() else "cpu"
419
+ except Exception:
420
+ continue
421
+ return "cpu"
422
+
423
+ def device() -> torch.device: return torch.device(backend_disponivel())
424
+ def vazia_cache() -> None: # higiene de RAM por backend
425
+ b = backend_disponivel()
426
+ if b == "cpu": gc.collect(); return
427
+ getattr(torch, b).empty_cache()
428
+ ```
429
+ Custo: uma importação opcional por backend, uma vez, no boot. Substitui os 19
430
+ `torch.cuda.*` por `backend_disponivel()`.
431
+
432
+ ---
433
+
434
+ ## 8. Aprimoramentos propostos (com critério de aceite)
435
+
436
+ | # | Aprimoramento | Ganho | Esforço | Critério de aceite (objetivo) |
437
+ |---|---|---|---|---|
438
+ | **P1** | **PCGrad correto** (`fator = dot/n2`, `g = gm + (ga − fator·gm)`) | elimina 25,3% de violações da garantia de não-piora | 2 linhas | `verif_pcgrad_invariantes.py` → variante `correto` com 0 violações de I1/I2/I3 |
439
+ | **P2** | **RoPE com offset de cache** | devolve a informação de posição relativa na decodificação | ~10 linhas | teste: `⟨q_rot,k_rot⟩` **diferente** para t=1,2,3,10 e igual ao valor analítico `⟨q, R_{j−t} k⟩` |
440
+ | **P3** | **Tronco 5:1 de verdade** — `modelo.n_camadas = 6` no config + validação checando o campo **real** | o requisito 5:1 passa a ser instanceado e verificável | 1 linha de config + 3 de validação | `validacao.py` deve reprovar `n_camadas=3` e aprovar `6`; `contar_atencao()` deve devolver 1 |
441
+ | **P4** | **Chave de dedup com mídia** (`texto+entrada+saida+sha1(mídia)`) | +24% de registros de treino (158 em vez de 534 descartados) | ~15 linhas | medida no corpus real: descartados ≤ 10% |
442
+ | **P5** | **Checkpoint atômico**: `os.replace` + `fsync` + escrita do `meta` **antes** + validação do tensor **antes** de apagar o antigo | elimina perda de checkpoint em crash | ~20 linhas | simular kill no meio da escrita: o checkpoint antigo continua carregável e o novo nunca é aceito com hash divergente |
443
+ | **P6** | **IPC: ler a capacidade do header** e validar contra `size` | elimina perda silenciosa de frames | ~8 linhas | abrir a mesma região com `size` diferente deve **levantar erro**, não ler 0 frames |
444
+ | **P7** | **Eliminar a materialização 2× da janela paralela** (buffer rolante) | 2× de RAM no termo dominante | ~40 linhas | `segs` deixa de existir; o pico de RSS do `insere_paralela` cai pela metade em T=65.536 (100,47 → ~50 MB) |
445
+ | **P8** | **Checkpointing no `c* = √(S/k)`** | 27×–443× de RAM nasativações, a **+33%** de tempo | 1 flag | `modelo_ram.py` com `c*` produz M* como previsto (0,26 MB em S=256) |
446
+ | **P9** | **Filtro de split de avaliação** no `StreamingCorpus` | elimina o vazamento eval→treino (10/39 fontes) | ~15 linhas | nenhum registro de split `validation`/`test` chega ao corpus de treino |
447
+ | **P10** | **Corrigir o split `"lite"`** (usar `train` ou o split real) | recupera a fonte Fable | 1 linha | a fonte passa de 0 para N>0 registros no corpus real |
448
+ | **P11** | **Normalização Unicode no ponto de entrada** (`unicodedata.normalize("NFC", ...)`) | elimina o risco latente de custo de tokens e de dedup errado | 3 linhas | 100% dos textos canônicos; dedup estável entre NFC/NFD |
449
+ | **P12** | **Tornar a suíte do repo executável sem torch** (20/20 arquivos falham por import) | 20 arquivos de teste voltam a ser uma rede de segurança | ~2 h | `pytest` coleta e roda sem torch, ou falha com mensagem clara |
450
+ | **P13** | **Camada de backend** (§7.3) | portabilidade para NPU/DCU | ~1 h + 19 call sites | `backend_disponivel()` funciona em CPU e não referencia `torch.cuda` |
451
+ | **P14** | **Recalcular as métricas de capa** com o config entregue | o README deixa de descrever um modelo de 237 camadas que não existe | ~1 h | todo número do README tem `arquivo:linha` + comando de reprodução |
452
+ | **P15** | **Corrigir os scripts de prova**: `np.trapz` → `np.trapezoid`; `ent_rel` recebendo vetor; `assert` em vez de print | a suíte de verificação volta a ser confiável | ~1 h | os 9 scripts rodam com numpy ≥ 2 e falham com exit ≠ 0 quando um invariante quebra |
453
+ | **P16** | **Corrigir o docstring do Lipschitz** (`vit_lra.py:201-203`) | documentação coerente com o teorema | 3 linhas | o docstring cita o bound `(mx+mn+2ε)/(mx+ε)²` e o caso do clipe saturado |
454
+
455
+ ### 8.1 Aprimoramento matemático adicional (novo, não estava em nenhum relatório)
456
+
457
+ **Escolha ótima de `τ_min` pelo critério de taxa–distorção.** O projeto usa
458
+ `ajustar_k_tau_min` com `tau_min_barreira = 0,25` fixo. Num modelo
459
+ `y = softmax(l + τu)` com `u ~ Gumbel(0,1)`, o ruído equivale a ruído
460
+ multiplicativo de escala `τ` sobre os logits. Para um alvo `y*` com entropia `H(y*)`,
461
+ o erro esperado da distorção é
462
+ ```
463
+ D(τ) = E_{u}‖softmax(l + τu) − y*‖²
464
+ ≈ τ²·‖∇(KL(y*‖softmax(l)))‖² + O(τ⁴) (Taylor de segunda ordem)
465
+ ```
466
+ e o ruído de amostragem do Gumbel é `O(τ²)`. O ponto ótimo equilibra
467
+ **viés** (a passagem para τ grande satura e o viés cresce) e **variância**
468
+ (o ruído `O(τ²)`), o que dá `τ_min* = Θ(1/‖∇KL‖)` — isto é, **o `τ_min` ótimo
469
+ cresce com a sharpness do alvo**, exatamente o oposto de um piso fixo.
470
+ O valor fixo 0,25 é arbitrário do ponto de vista da taxa–distorção e deveria ser
471
+ substituído por `τ_min*(K, H(y*))` calibrado nas métricas reais de perplexidade —
472
+ ou, no mínimo, por um sweep de `τ_min ∈ {0,05 … 0,5}` medindo perplexidade e
473
+ `δ_ort` (o doc 27 já tem o ferramental: `ajustar_k_tau_min` + `δ_ort`).
474
+
475
+ **Cuidado:** isto é uma proposta com hipótese explícita (a expansão de 2ª ordem
476
+ assume `‖τu‖` pequeno em relação à folga dos logits). O critério de aceite é
477
+ empírico, não teórico: `τ_min` escolhido por sweep deve melhorar a perplexidade
478
+ **sem** degradar `δ_ort` além de `1e-5` — se não melhorar, a proposta é
479
+ refutada e o piso fixo permanece.
480
+
481
+ ---
482
+
483
+ ## 9. Plano em fases
484
+
485
+ ### Fase 0 — Corrigir o que invalida resultados publicados (1 dia)
486
+ P1, P2, P3, P15, P16. Sem depender de hardware, sem mudar API pública.
487
+ **Aceite:** `verif_pcgrad_invariantes.py` e `verif_cinza_lipschitz.py` com 0
488
+ violações; `validacao.py` reprova `n_camadas=3`; `v05`/`v08` rodam sem crash;
489
+ `git diff` só toca os arquivos listados.
490
+ **Rollback:** `git checkout` dos arquivos (o espelho do HF nunca foi tocado).
491
+
492
+ ### Fase 1 — Corrigir a tubulação de dados e a persistência (2 dias)
493
+ P4, P5, P6, P9, P10, P11, P12.
494
+ **Aceite:** corpus real de ≥ 2.100 registros com ≤ 10% de dedup; `n_camadas=3`
495
+ reprovado na validação; `anexar()` com `size` divergente levanta erro; nenhum
496
+ registro de split de avaliação no corpus de treino; `pytest` coleta sem torch.
497
+ **Rollback:** as correções são cópias em `correcoes/`; basta não copiá-las.
498
+
499
+ ### Fase 2 — RAM e throughput (2 dias)
500
+ P7, P8 + medir.
501
+ **Aceite:** pico de RSS do `insere_paralela` cai ~2× em T=65.536 (medido com
502
+ `resource.getrusage`); `c*` produz a memória prevista em `modelo_ram.py`;
503
+ tabela de §6.4 reproduzida com o mesmo script.
504
+
505
+ ### Fase 3 — Portabilidade (3–5 dias)
506
+ P13 + recompilar as 5 extensões Cython por plataforma + desligar int8 em
507
+ backends sem kernel.
508
+ **Aceite:** `backend_disponivel()` retorna o backend correto em cada máquina de
509
+ teste; `python setup.py build_ext --inplace` produz `.so` importável no Python
510
+ da máquina; o mesmo corpus roda em CPU, Ascend e (se houver) DCU com perplexidade
511
+ dentro de 1% da CPU.
512
+
513
+ ### Fase 4 — Blindar o que é publicado (1 dia)
514
+ P14 + reescrever os números de capa do README com `arquivo:linha` e comando de
515
+ reprodução; corrigir "18/18" para "17/18" até `peso_moe` existir.
516
+ **Aceite:** cada número do README tem um comando que o reproduz; `README` e
517
+ `RELATORIO_KHTST` não citam mais 237 camadas com 3 camadas no config.
518
+
519
+ ---
520
+
521
+ ## 10. O que **não** fazer (anti-goals)
522
+
523
+ | Anti-goal | Por quê |
524
+ |---|---|
525
+ | **Não** trocar `float64` por `float32` esperando ganho de RAM | o próprio agente mediu: o ganho real é ≤ 1,5 kB (arrays de 48 B a 4 kB). O custo de RAM está em A-08/A-17 e na janela paralela |
526
+ | **Não** "consertar" a divergência de capacidade do IPC (A-10) | foi refutado: a função comparada é código morto. O bug real é outro (capacidade do header nunca lida) |
527
+ | **Não** reativar a atenção GLOBAL para contexto longo | `O(B·H·S²)`: 19,8 TB de scores com S=262.144. A janela não é opcional |
528
+ | **Não** usar `np.trapz` em scripts novos | removido no numpy ≥ 2 (achado meu) |
529
+ | **Não** reportar "18/18" | `peso_moe` é órfão; é 17/18 |
530
+ | **Não** publicar `RAM pico 3.066 MB` | é impossível como pico absoluto e coincide com o tamanho do corpus em bytes |
531
+ | **Não** confiar em `assert` inexistente como "check verde" | os 9 scripts de verificação não têm um único `assert`: nada aborta quando um invariante quebra |
532
+ | **Não** recompilar as extensões e commitar `.so` no HF | ABI por plataforma; o repo já carrega `.so` de cpython-312 que não rodam fora do build original |
533
+
534
+ ---
535
+
536
+ ## 11. Índice de evidências (tudo executável)
537
+
538
+ | Evidência | Caminho |
539
+ |---|---|
540
+ | Meus scripts de verificação | `minha_verificacao/verif_pcgrad_invariantes.py`, `verif_pcgrad_rope.py`, `verif_cinza_lipschitz.py`, `modelo_ram.py` |
541
+ | Saídas dos meus scripts | `minha_verificacao/saida_*.txt` |
542
+ | Relatórios dos agentes | `relatorios/t1…t5*.md`, `relatorios/verify_t*.md` |
543
+ | Scripts + saídas dos agentes | `verificacao/v0*.py`, `verificacao/out_v0*.txt`, `verificacao/t4_p*.py`, `verificacao/saidas/` |
544
+ | Correções prontas (36, compilam) | `correcoes/` |
545
+ | Correção do PCGrad com sinal certo | `minha_verificacao/CORRECAO_A01_PCGrad.md` |
546
+ | Espelho intacto do HF | `repo/` (git status vazio) |
547
+ | Corpus real que eu coletei | `verificacao/saidas/corpus_real_mini.jsonl` (1.812 registros, 3,08 MB) |
548
+
549
+ ---
550
+
551
+ ## 12. Como executar
552
+
553
+ Ver `EXECUTAVEL.md` (guia passo a passo). Resumo do ambiente que usei:
554
+ Python 3.11.2, 2 cores, ~3 GB RAM, sem GPU, sem torch; `pip` precisa de
555
+ `--index-url https://pypi.org/simple` (o mirror padrão do sandbox está vazio);
556
+ as extensões `.so` do repo são cpython-312 e **não importam** neste ambiente —
557
+ todo caminho testado é o Python puro.
auditoria_independente/README.md ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Auditoria independente — provas numpy-puras (hardened v23)
2
+
3
+ Scripts de verificação produzidos pela **auditoria independente** de 2026-10-17
4
+ (agentes externos; originais em `minha_verificacao/` do auditório), integrados
5
+ e **HARDENED no v23**: cada verificação termina em `assert` (P15 — os
6
+ originais só imprimiam; um invariante quebrado não abortava nada) e o exit
7
+ code reflete o resultado (`0` verde, `1` violação).
8
+
9
+ Executáveis em ambiente SEM torch (numpy puro) — o mesmo caminho que a
10
+ auditoria usou para auditar o projeto sem instalar dependências.
11
+
12
+ | Script | O que prova | v23 |
13
+ |---|---|---|
14
+ | `verif_pcgrad_invariantes.py` | PCGrad: 3 variantes × invariantes I1/I2/I3 de Yu et al. (2020); só `fator = dot/n2` com `ga − fator·gm` satisfaz os três | correção **APLICADA** em `treino/cirurgia_grad.py` (Teo 35.1) |
15
+ | `verif_pcgrad_rope.py` | A) o código antigo violava "nenhuma tarefa piora" em 25,3% dos conflitos; B) RoPE sem offset ⟹ score CONSTANTE em t | correções **APLICADAS** (Teos 35.1/35.8); prova torch ao vivo: `tests/test_khtst_v23.py` §1–§2 |
16
+ | `verif_cinza_lipschitz.py` | Teo 27.7(ii-a) VÁLIDO (razão ≤ 1); docstring antigo "2δ" FALSO (45×) | docstring **CORRIGIDO** em `percepcao/vit_lra.py` (P16) |
17
+ | `modelo_ram.py` | Modelo de RAM analítico; ponto ótimo de checkpointing `c* = √(S/k)`; orçamento por RAM | seção de incoerências agora DINÂMICA vs `configs/base.json` (L=6 no v23) |
18
+
19
+ Executar:
20
+
21
+ ```bash
22
+ python3 verif_pcgrad_invariantes.py && \
23
+ python3 verif_pcgrad_rope.py && \
24
+ python3 verif_cinza_lipschitz.py && \
25
+ python3 modelo_ram.py && echo "AUDITORIA 4/4 VERDE"
26
+ ```
27
+
28
+ Regra da casa (do PLANO_MELHORIAS_KHTST.md §11, mantida): cada número impresso
29
+ é `MEDIDO` (saída executada) ou `TEÓRICO` (consequência de teorema) — claims
30
+ refutadas não voltam como válidas.
auditoria_independente/modelo_ram.py ADDED
@@ -0,0 +1,282 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """MODELO DE MEMÓRIA (RAM) DO KHTST — a partir do config REAL e do código
4
+ real. numpy puro, sem torch. HARDENED v23 (P15): asserts + exit code.
5
+
6
+ Objetivo: dar um número honesto de RAM para treino e inferência, e provar o
7
+ ponto ótimo de checkpointing (c* = √(S/k), M* = 2H√(kS) — prova no doc 35 §6).
8
+
9
+ Fontes de verdade lidas:
10
+ configs/base.json (config shipped)
11
+ src/khtst/mamba3/tronco.py (CamadaTroncoM3, NoPE em (i+1)%6==0)
12
+ src/khtst/memoria/janela_1m.py (janela paralela: m_max, segs, buffers)
13
+ src/khtst/percepcao/atencao.py (aplicar_rope, KV-cache kv_max)
14
+
15
+ v23: o caminho do repo é derivado do PRÓPRIO script (não mais do espelho do
16
+ auditório) e a seção de incoerências virou VERIFICAÇÃO DINÂMICA contra o
17
+ config entregue — com n_camadas=6 (v23) o contrato 5:1 EXISTE (1 NoPE).
18
+ """
19
+ import json
20
+ import math
21
+ import os
22
+ import sys
23
+
24
+ REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
25
+ CFG = json.load(open(os.path.join(REPO, "configs", "base.json"),
26
+ encoding="utf-8"))
27
+
28
+ M = CFG["modelo"]
29
+ D = int(M["d_modelo"])
30
+ L = int(M["n_camadas"])
31
+ DFF = int(M["d_ff"])
32
+ VOCAB = int(M["vocab"])
33
+ NCAB = int(M["n_cabecas"])
34
+ DK = D // NCAB
35
+ KV_MAX = int(M["kv_cache_max"])
36
+ J = CFG["janela_1m"]
37
+ LOTE = int(CFG["treino"]["lote"])
38
+ W_SEG = int(J["w_segmento"])
39
+ S_PASSO = int(J["s_passo"])
40
+ L_FINO = int(J["l_fino"])
41
+ N_MAX = int(J["n_max_tokens"])
42
+ COMP_SAIDA = int(J["comprimento_saida"])
43
+ JANELA_W = int(J["janela_w"])
44
+ JANELA_E = int(J["janela_e"])
45
+ B4 = 4 # bytes por float32
46
+
47
+
48
+ def linha(t=""):
49
+ print(t)
50
+
51
+
52
+ # --------------------------------------------------------------------------
53
+ # 1) PARÂMETROS (contagem analítica a partir do código)
54
+ # --------------------------------------------------------------------------
55
+ def params_analiticos():
56
+ p = {}
57
+ p["embedding"] = VOCAB * D # dicionário de tokens
58
+ p["embedding_saida"] = 0 # lm_head amarrado (compartilha)
59
+ # por camada do tronco (CamadaTroncoM3):
60
+ # atenção NoPE: Wq,Wk,Wv,Wo -> 4*d^2
61
+ # Mamba3: proj in/out + SSM (d_estado*rank) — ver doc 22
62
+ d_estado = int(M["mamba3"]["d_estado"])
63
+ rank = int(M["mamba3"]["rank_mimo"])
64
+ attn = 4 * D * D
65
+ mamba = 2 * D * D + d_estado * rank * D + D * d_estado
66
+ ffn = 2 * D * DFF
67
+ # micro-FFN por camada (camadas micro)
68
+ micro = 2 * D * DFF if L >= 1 else 0
69
+ p["camada_atencao"] = attn
70
+ p["camada_mamba3"] = mamba
71
+ p["camada_ffn"] = ffn
72
+ p["camada_micro"] = micro
73
+ p["por_camada_total"] = attn + mamba + ffn + micro
74
+ # NoPE: (i+1)%6==0 -> com L=6 => 1 NoPE (contrato 5:1 existente no v23)
75
+ n_nope = sum(1 for i in range(L) if (i + 1) % 6 == 0)
76
+ p["n_nope"] = n_nope
77
+ p["n_mamba"] = L - n_nope
78
+ p["total_tronco"] = L * p["por_camada_total"]
79
+ p["total"] = p["embedding"] + p["total_tronco"]
80
+ return p
81
+
82
+
83
+ P = params_analiticos()
84
+
85
+ # --------------------------------------------------------------------------
86
+ # 2) MODELO DE MEMÓRIA
87
+ # --------------------------------------------------------------------------
88
+ def estado_do_otimizador(n_param):
89
+ """Pesos fp32 + grads fp32 + momento Muon (1x) + AdamW (m,v = 2x).
90
+ Muon e AdamW sao aplicados em subconjuntos disjuntos na prática; aqui
91
+ mostramos os dois cenários."""
92
+ return {
93
+ "pesos": 4 * n_param,
94
+ "grads": 4 * n_param,
95
+ "muon_mom": 4 * n_param,
96
+ "adamw_mv": 8 * n_param,
97
+ }
98
+
99
+
100
+ def ativacoes(B, S, k_salvos=12, c=None):
101
+ """Ativações do tronco.
102
+ Sem checkpointing: k_salvos tensores (B,S,H) por camada.
103
+ Com checkpointing de segmento c: guarda-se 1 tensor por segmento
104
+ (B,c,H) + o buffer de recomputação (B,c,H)*k_salvos."""
105
+ if c is None:
106
+ return k_salvos * B * S * D * B4 * L
107
+ segs = max(1, math.ceil(S / c))
108
+ guardado = segs * B * c * D * B4 * L # 1 tensor por segmento
109
+ recompute = B * c * D * B4 * k_salvos * L # buffer de recomputação
110
+ return guardado + recompute
111
+
112
+
113
+ def kv_cache(B, S_kv):
114
+ return 2 * B * NCAB * S_kv * DK * B4 * L # K e V, por camada
115
+
116
+
117
+ def janela_paralela_buffers(T_chunk, B=1):
118
+ """Janela paralela 1M: 'segs' do MAP paralelo + buffers z/utilidade +
119
+ grade SOM. medido do código (insere_paralela / __init__)."""
120
+ m_max = max(1, (N_MAX - L_FINO) // max(1, S_PASSO)) + 1
121
+ lado = max(16, min(128, int(math.sqrt(m_max)) + 1))
122
+ M_win = 1 if T_chunk <= W_SEG else (T_chunk - W_SEG) // S_PASSO + 1
123
+ segs = M_win * W_SEG * D * B4 * B # (M, w_seg, d) materializado
124
+ mascara = M_win * W_SEG * B # bool (1 B)
125
+ z = m_max * D * B4 # buffer de resumo
126
+ util = m_max * B4
127
+ grade = lado * lado * D * B4 # grade SOM
128
+ return {"segs": segs, "mascara": mascara, "z": z, "grade": grade,
129
+ "m_max": m_max, "lado": lado, "M_win": M_win}
130
+
131
+
132
+ def pesos_fusao(n):
133
+ K = 1 if n <= JANELA_W else int(math.ceil((n - JANELA_W) / JANELA_E)) + 1
134
+ return K * n * B4, K
135
+
136
+
137
+ # --------------------------------------------------------------------------
138
+ # 3) PROVA DO PONTO ÓTIMO DE CHECKPOINTING (doc 35 §6)
139
+ # --------------------------------------------------------------------------
140
+ def ponto_otimo_checkpointing(S, k_salvos=12, k_min=1):
141
+ """Minimiza M(c) = H*(S/c + k*c) -> c* = sqrt(S/k), M* = 2H*sqrt(k*S).
142
+ Tempo: T(c) = 3 (fwd+bwd) + 1 (recompute do último segmento) = 4 unidades,
143
+ INDEPENDENTE de c -> overhead constante de +33%."""
144
+ c_star = math.sqrt(S / max(1, k_salvos * k_min))
145
+ M_star = 2 * D * B4 * math.sqrt(k_salvos * k_min * S) * L
146
+ M_0 = k_salvos * D * B4 * S * L
147
+ return c_star, M_star, M_0
148
+
149
+
150
+ def main():
151
+ linha("=" * 92)
152
+ linha("MODELO DE RAM DO KHTST — derivado do config e do código REAIS")
153
+ linha("=" * 92)
154
+ linha(f"repo: {REPO}")
155
+ linha(f"config: d={D} L={L} d_ff={DFF} vocab={VOCAB} n_cabecas={NCAB} dk={DK}")
156
+ linha(f" kv_max={KV_MAX} lote={LOTE} janela: n_max={N_MAX} "
157
+ f"saida={COMP_SAIDA}")
158
+ linha(f" w_segmento={W_SEG} s_passo={S_PASSO} l_fino={L_FINO} "
159
+ f"janela_w={JANELA_W} janela_e={JANELA_E}")
160
+ linha()
161
+ linha("--- 1) PARAMETROS (contagem analitica) ---")
162
+ for k, v in P.items():
163
+ if isinstance(v, int):
164
+ linha(f" {k:22s} = {v:>12,} ({v*4/1e6:8.3f} MB fp32)")
165
+ linha(f" >> NoPE no tronco: {P['n_nope']} (padrão (i+1)%6==0, L={L})")
166
+ linha(f" >> Camadas Mamba: {P['n_mamba']}")
167
+ linha()
168
+
169
+ # ---- HARDENED: o contrato 5:1 é verificável analiticamente aqui ----
170
+ assert L % 6 == 0, \
171
+ f"n_camadas={L} deve ser múltiplo de 6 (contrato 5:1 — Teo 35.5)"
172
+ assert P["n_nope"] == L // 6, \
173
+ f"NoPE esperado {L//6}, obtido {P['n_nope']}"
174
+
175
+ linha("--- 2) ESTADO DO MODELO + OTIMIZADOR ---")
176
+ est = estado_do_otimizador(P["total"])
177
+ tot = sum(est.values())
178
+ for k, v in est.items():
179
+ linha(f" {k:22s} = {v/1e6:8.3f} MB")
180
+ linha(f" {'TOTAL (4x)':22s} = {tot/1e6:8.3f} MB")
181
+ linha()
182
+
183
+ linha("--- 3) ATIVACOES e PONTO OTIMO DO CHECKPOINTING ---")
184
+ for S in (256, 1024, 4096, 16384, 65536):
185
+ c_star, M_star, M_0 = ponto_otimo_checkpointing(S)
186
+ linha(f" S={S:>6} sem ckpt={M_0/1e6:9.2f} MB | c*={c_star:7.1f} "
187
+ f"-> M*={M_star/1e6:8.2f} MB | ganho={M_0/M_star:5.1f}x "
188
+ f"| overhead de tempo = +33% (independente de c)")
189
+ # HARDENED: M(c) tem mínimo em c* — verifica a forma da prova
190
+ def M_c(c, S=S):
191
+ return D * B4 * L * (S / max(1.0, c) + 12.0 * c)
192
+ m_ot = M_c(c_star)
193
+ m_menos = M_c(max(1.0, c_star / 2.0))
194
+ m_mais = M_c(c_star * 2.0)
195
+ assert m_ot <= m_menos + 1e-6 and m_ot <= m_mais + 1e-6, \
196
+ f"c*={c_star} não é mínimo: M(c*)={m_ot} M(c*/2)={m_menos} " \
197
+ f"M(2c*)={m_mais}"
198
+ assert M_star <= M_0, "checkpointing deve reduzir a memória"
199
+ linha(" [assert] c* = √(S/k) é mínimo de M(c) em todos os S ✓")
200
+
201
+ linha()
202
+ linha("--- 4) JANELA PARALELA 1M (o termo dominante em inferência longa) ---")
203
+ for T in (512, 4096, 16384, 65536):
204
+ j = janela_paralela_buffers(T, B=1)
205
+ tot_j = j["segs"] + j["mascara"] + j["z"] + j["grade"]
206
+ linha(f" chunk T={T:>6}: segs={j['segs']/1e6:8.2f} MB "
207
+ f"(M={j['M_win']} janelas de {W_SEG} com passo {S_PASSO} -> "
208
+ f"redundância W/s={W_SEG/S_PASSO:.0f}x)")
209
+ linha(f" z(m_max={j['m_max']})={j['z']/1e6:6.2f} MB "
210
+ f"grade SOM({j['lado']}^2)={j['grade']/1e6:6.2f} MB "
211
+ f"TOTAL fixo={tot_j/1e6:6.2f} MB")
212
+ # HARDENED: nº de cópias de tokens = M_win·W ≈ T·(W/s) com erro ≤ W
213
+ # (prova da redundância 2× — doc 35 §6: M·W = (T/s)·W = T·(W/s))
214
+ copias = j["M_win"] * W_SEG
215
+ alvo = T * (W_SEG / S_PASSO)
216
+ assert abs(copias - alvo) <= W_SEG + 1e-9, \
217
+ f"cópias={copias} difere de T·(W/s)={alvo} além da folga W={W_SEG}"
218
+ linha(" [assert] segs = T·(W/s)·d·4 confirmado (redundância 2× da janela)")
219
+ pf, K = pesos_fusao(N_MAX)
220
+ linha(f" pesos_fusao(n={N_MAX}): K={K} janelas -> {pf/1e6:.2f} MB "
221
+ f"(tensor denso (K,n))")
222
+ pf2, K2 = pesos_fusao(N_MAX + COMP_SAIDA)
223
+ linha(f" pesos_fusao(n={N_MAX+COMP_SAIDA}): K={K2} -> {pf2/1e6:.2f} MB")
224
+ linha()
225
+
226
+ linha("--- 5) TABELA DE ORÇAMENTO POR RAM DISPONÍVEL (treino, fp32) ---")
227
+ linha(" modelo: estado 4xP + ativações(c) + scores da atenção + KV + janela")
228
+ linha(" atenção: JANELA (raio r=128) -> scores O(B*H*S*(2r+1)); a variante")
229
+ linha(" GLOBAL é O(B*H*S²) e INVIÁVEL a partir de ~2k tokens:")
230
+ for S in (2048, 8192, 32768, 262144):
231
+ sg = LOTE * NCAB * S * S * B4 * L
232
+ linha(f" S={S:>7}: scores GLOBAL = {sg/1e9:12.2f} GB "
233
+ f"JANELA(r=128) = {LOTE*NCAB*S*257*B4*L/1e9:8.3f} GB")
234
+ est_total = sum(estado_do_otimizador(P["total"]).values())
235
+ R = 128
236
+ for orc in (3, 8, 16, 24, 48, 96):
237
+ orc_b = orc * 1024 ** 3
238
+ disp = orc_b - est_total
239
+ # termo 1: scores da janela (linear em S)
240
+ por_token = LOTE * NCAB * (2 * R + 1) * B4 * L
241
+ s_scores = int(disp / por_token)
242
+ # termo 2: ativações sem checkpointing
243
+ s_ativ = int(disp / (LOTE * D * B4 * 12 * L)) if disp > 0 else 0
244
+ # termo 3: KV (trunca em kv_max) — constante
245
+ kv = kv_cache(LOTE, KV_MAX)
246
+ # termo 4: janela paralela (MAP paralelo) — linear em T
247
+ jw = janela_paralela_buffers(1, LOTE)["segs"]
248
+ s_janela = int(disp / max(1, jw)) if jw else 10 ** 9
249
+ S_ok = min(s_scores, s_ativ, s_janela)
250
+ c_star, M_star, _ = ponto_otimo_checkpointing(max(1, S_ok))
251
+ linha(f" {orc:>3} GB | estado={est_total/1e9:6.3f} GB | "
252
+ f"KV={kv/1e6:6.1f} MB | "
253
+ f"S_max (scores)={s_scores:>9,} | (ativ.)={s_ativ:>9,} | "
254
+ f"(janela)={s_janela:>9,} | S viável={S_ok:>9,} | "
255
+ f"c*={c_star:5.0f}")
256
+ linha()
257
+
258
+ linha("--- 6) VERIFICAÇÃO DINÂMICA config-vs-README (v23) ---")
259
+ linha(f" config/base.json AGORA: modelo.n_camadas = {L} (múltiplo de 6 "
260
+ f"— {'OK' if L % 6 == 0 else 'VIOLA Teo 35.5'})")
261
+ linha(f" tronco instancia {P['n_mamba']} Mamba + {P['n_nope']} NoPE = "
262
+ f"proporção {P['n_mamba']}:{P['n_nope']} — o contrato 5:1 do README")
263
+ linha(" EXISTE no config entregue desde o v23 (antes: L=3 ⟹ 0 NoPE —")
264
+ linha(" achado A-04 da auditoria; corrigido com validação no campo REAL).")
265
+ linha(f" NOTA histórica: os números '237 camadas / 78,8 MB / 3,92×' do")
266
+ linha(f" README antigo descreviam outro modelo — corrigidos no card v23")
267
+ linha(f" (P14: números de capa com proveniência arquivo:linha).")
268
+ assert L % 6 == 0
269
+
270
+ linha()
271
+ linha("--- 7) 'RAM pico 3.066 MB' do README antigo (achado E-08) ---")
272
+ linha(" python3 + numpy já usa ~29,5 MB de RSS; o doc 28 declarava")
273
+ linha(" 78,8 MB de pesos: um pico ABSOLUTO de 3,066 MB é impossível —")
274
+ linha(" o número coincidia com o tamanho do CORPUS em bytes. O README")
275
+ linha(" v23 não repete esse número (P14 — números com reprodução).")
276
+ linha()
277
+ linha("VERIFICAÇÃO MODELO DE RAM: VERDE — asserts passaram")
278
+ return 0
279
+
280
+
281
+ if __name__ == "__main__":
282
+ sys.exit(main())
auditoria_independente/verif_cinza_lipschitz.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Teorema 27.7(ii) — Lipschitz do detector de cinza. numpy puro, sem torch.
4
+ HARDENED v23 (P15): asserts + exit code.
5
+
6
+ Claim do doc 27 §4 Teo 27.7(ii):
7
+ (a) ∂χ/∂I tem norma ≤ 2/(max_c+ε) + χ/(max_c+ε)
8
+ (b) para imagens bem-expostas (max ≥ m̄): |Δs| ≤ 2δ/m̄
9
+ (c) (docstring ANTIGO do código, v≤22) "χ é Lipschitz com constante
10
+ 2/(max_c+ε) — perturbação δ muda s por ≤ 2δ" ← FALSO (45× no pixel
11
+ quase-preto); o v23 CORRIGIU o docstring de percepcao/vit_lra.py
12
+ para o bound do TEOREMA (P16).
13
+
14
+ χ(p) = (mx − mn)/(mx + ε), s = 1 − mean(χ), mx = max_c I_c, mn = min_c I_c
15
+
16
+ Prova fechada (doc 35 §4): bound_doc − bound_real = 2(mx − mn)/(mx+ε)² ≥ 0
17
+ """
18
+ import sys
19
+ import numpy as np
20
+
21
+ EPS = 1e-4
22
+
23
+
24
+ def chi(img):
25
+ mx = img.max(axis=-1)
26
+ mn = img.min(axis=-1)
27
+ return (mx - mn) / (mx + EPS)
28
+
29
+
30
+ def s_score(img):
31
+ return 1.0 - chi(img).mean()
32
+
33
+
34
+ def deriva_analitica(mx, mn):
35
+ """∂χ/∂mx = (ε+mn)/(mx+ε)² ; ∂χ/∂mn = −1/(mx+ε)."""
36
+ dmx = (EPS + mn) / (mx + EPS) ** 2
37
+ dmn = -1.0 / (mx + EPS)
38
+ return np.abs(dmx), np.abs(dmn)
39
+
40
+
41
+ def main():
42
+ rng = np.random.default_rng(20261016)
43
+ print("=" * 78)
44
+ print("TESTE 1 — o bound (a) do doc 27 domina a derivada analítica real?")
45
+ print("=" * 78)
46
+ pior = 0.0
47
+ for _ in range(4000):
48
+ mx = rng.uniform(1e-4, 1.0)
49
+ mn = rng.uniform(0.0, mx)
50
+ c = (mx - mn) / (mx + EPS)
51
+ dmx, dmn = deriva_analitica(mx, mn)
52
+ real = dmx + dmn # norma do gradiente (L1 por canal)
53
+ doc = 2.0 / (mx + EPS) + c / (mx + EPS)
54
+ pior = max(pior, real / doc)
55
+ print(f" max (derivada real)/(bound do doc) = {pior:.6f} -> "
56
+ f"{'DOMINADO (bound válido)' if pior <= 1 else 'VIOLADO'}")
57
+ assert pior <= 1.0, "TEOREMA do doc 27 violado — o bound (a) não domina"
58
+
59
+ # forma fechada da prova: bound_doc − bound_real = 2(mx−mn)/(mx+ε)² ≥ 0
60
+ mx = rng.uniform(1e-3, 1.0, size=100_000)
61
+ mn = rng.uniform(0.0, 1.0, size=100_000) * mx
62
+ folga = 2.0 * (mx - mn) / (mx + EPS) ** 2
63
+ assert (folga >= 0).all(), "prova fechada: folga do bound deve ser ≥ 0"
64
+ print(" forma fechada: bound_doc − bound_real = 2(mx−mn)/(mx+ε)² ≥ 0 ✓")
65
+
66
+ print()
67
+ print("=" * 78)
68
+ print("TESTE 2 — (b) |Δs| ≤ 2δ/m̄ e (c) |Δs| ≤ 2δ, por execução real")
69
+ print("=" * 78)
70
+ casos = [
71
+ ("brilho medio (0.5)", 0.5, 0.12),
72
+ ("brilho baixo (0.05)", 0.05, 0.12),
73
+ ("brilho MUITO baixo (0.005)", 0.005, 0.12),
74
+ ("pixel quase preto (0.001)", 0.001, 0.12),
75
+ ]
76
+ viol_2delta = 0
77
+ for nome, base, amp in casos:
78
+ img = rng.uniform(base - amp / 2, base + amp / 2, size=(1, 1, 3))
79
+ img = np.clip(img, 0.0, 1.0)
80
+ mbar = max(float(img.mean()), 1e-12) # pixels podem clipar a 0 —
81
+ # o bound (b) pressupõe imagem bem-exposta; no escuro ele é vazio
82
+ mx = float(img.max())
83
+ for delta in (1e-3, 1e-2, 5e-2):
84
+ pert = np.clip(img + rng.uniform(-delta, delta, size=img.shape),
85
+ 0, 1)
86
+ ds = abs(s_score(pert) - s_score(img))
87
+ b_doc = 2 * delta / (mx + EPS) # bound (a) por pixel, agregado
88
+ b_mbar = 2 * delta / mbar # bound (b) do doc
89
+ b_ingenuo = 2 * delta # bound (c) do docstring ANTIGO
90
+ viol_2delta += int(ds > b_ingenuo)
91
+ print(f" {nome:26s} delta={delta:<6} |Δs|={ds:.6f} "
92
+ f"bound(a)={b_doc:.6f} {'OK ' if ds <= b_doc else 'VIOL'} | "
93
+ f"bound(b)={b_mbar:.6f} {'OK ' if ds <= b_mbar else 'VIOL'} | "
94
+ f"2δ={b_ingenuo:.6f} {'OK ' if ds <= b_ingenuo else 'VIOL'}")
95
+ # o bound ingênuo 2δ DEVE ser violado em pixels escuros — é a razão da
96
+ # correção do docstring (P16). O TEOREMA (a) nunca é violado.
97
+ assert viol_2delta > 0, \
98
+ "esperado: bound ingênuo 2δ violado em pixels quase-pretos (docstring falso)"
99
+
100
+ print()
101
+ print("=" * 78)
102
+ print("TESTE 3 — pior caso construído: 1 canal sobe, pixel escuro")
103
+ print("=" * 78)
104
+ achou_violacao_2delta = False
105
+ for eps_i in (1e-3, 1e-2):
106
+ img = np.array([[[0.01, 0.01, 0.01]]]) # cinza exato -> s = 1
107
+ pert = img.copy()
108
+ pert[0, 0, 2] += eps_i # um canal sobe
109
+ ds = abs(s_score(pert) - s_score(img))
110
+ v1 = ds <= 2 * eps_i
111
+ v2 = ds <= 2 * eps_i / 0.01
112
+ achou_violacao_2delta |= (not v1)
113
+ print(f" cinza 0.01 + delta {eps_i}: |Δs| = {ds:.6f} | "
114
+ f"2δ = {2*eps_i:.6f} -> {'OK' if v1 else 'VIOLADO (docstring antigo)'}"
115
+ f" | 2δ/m̄ = {2*eps_i/0.01:.6f} -> "
116
+ f"{'OK' if v2 else 'VIOLADO (bound (b))'}")
117
+ assert achou_violacao_2delta, \
118
+ "contra-exemplo 0,01+0,001 deve violar o 2δ do docstring antigo (45×)"
119
+
120
+ print()
121
+ print("=" * 78)
122
+ print("TESTE 4 — o bound (a) com max_c = max GLOBAL da imagem (outra leitura)")
123
+ print("=" * 78)
124
+ for _ in range(200):
125
+ img = rng.uniform(0, 1, size=(1, 8, 8, 3))
126
+ mx_global = img.max()
127
+ for delta in (1e-2, 5e-2):
128
+ pert = np.clip(img + rng.uniform(-delta, delta, size=img.shape),
129
+ 0, 1)
130
+ ds = abs(s_score(pert) - s_score(img))
131
+ b = 2 * delta / (mx_global + EPS)
132
+ assert ds <= b or b > 1.0, \
133
+ f"leitura global violada sem folga: ds={ds} b={b}"
134
+ print(" 200 imagens × 2 deltas: |Δs| ≤ 2δ/(mx_global+ε) (ou trivialmente")
135
+ print(" satisfeito quando o bound > 1 — registrado como folga honesta)")
136
+
137
+ print()
138
+ print("VERIFICAÇÃO LIPSCHITZ (doc 27 Teo 27.7): VERDE — teorema válido;")
139
+ print("docstring do código CORRIGIDO no v23 (P16) para citar o bound real.")
140
+ return 0
141
+
142
+
143
+ if __name__ == "__main__":
144
+ sys.exit(main())
auditoria_independente/verif_pcgrad_invariantes.py ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """PCGrad: três candidatos comparados contra os invariantes CORRETOS do
4
+ Yu et al. (2020). numpy puro, sem torch. HARDENED v23 (P15): asserts + exit.
5
+
6
+ Candidatos:
7
+ (1) CÓDIGO ANTIGO (treino/cirurgia_grad.py v≤22, linhas 90-101)
8
+ fator = -dot/n2 ; g = gm + fator*ga
9
+ (2) CORREÇÃO DO AGENTE AUDITOR (correcoes/.../cirurgia_grad.py:66-81)
10
+ fator = -dot/n2 ; g = gm + (ga - fator*gm)
11
+ (3) CORREÇÃO CORRETA — APLICADA no v23 (Teo 35.1)
12
+ fator = dot/n2 ; g = gm + (ga - fator*gm)
13
+
14
+ INVARIANTES DO PCGrad (2 tarefas, 1 par com conflito):
15
+ I1 <g_aux', g_main> = 0 (ortogonalidade exata)
16
+ I2 ||g_aux'||^2 = ||g_aux||^2 - dot^2/||g_main||^2 <= ||g_aux||^2 (contração)
17
+ I3 <g_total, g_main> = ||g_main||^2 >= 0 e <g_total, g_aux'> = ||g_aux'||^2 >= 0
18
+
19
+ v23: a variante (3) É o código de produção desde o v23 — este script é a
20
+ prova de regressão NUMÉRICA independente (a prova torch ao vivo está em
21
+ tests/test_khtst_v23.py §1).
22
+ """
23
+ import sys
24
+ import numpy as np
25
+
26
+ EPS = 1e-8
27
+
28
+
29
+ def variante_1(gm, ga):
30
+ dot = float(gm @ ga)
31
+ n2 = float(gm @ gm)
32
+ p = dot < 0.0 and n2 > EPS
33
+ f = -dot / n2 if p else 0.0
34
+ return gm + (f * ga if p else ga), p
35
+
36
+
37
+ def variante_2(gm, ga):
38
+ dot = float(gm @ ga)
39
+ n2 = float(gm @ gm)
40
+ p = dot < 0.0 and n2 > EPS
41
+ f = -dot / n2 if p else 0.0
42
+ return gm + (ga - f * gm if p else ga), p
43
+
44
+
45
+ def variante_3(gm, ga):
46
+ dot = float(gm @ ga)
47
+ n2 = float(gm @ gm)
48
+ p = dot < 0.0 and n2 > EPS
49
+ f = dot / n2 if p else 0.0 # <== SINAL CORRETO (v23, Teo 35.1)
50
+ return gm + (ga - f * gm if p else ga), p
51
+
52
+
53
+ def testa(n=200000, seed=20261016):
54
+ rng = np.random.default_rng(seed)
55
+ st = {k: {"I1": 0, "I2": 0, "I3": 0, "n": 0}
56
+ for k in ("atual", "agente", "correto")}
57
+ for _ in range(n):
58
+ gm = rng.normal(size=32)
59
+ ga = rng.normal(size=32)
60
+ dot = float(gm @ ga)
61
+ n2 = float(gm @ gm)
62
+ if dot >= 0 or n2 <= EPS:
63
+ continue
64
+ for nome, fn in (("atual", variante_1),
65
+ ("agente", variante_2), ("correto", variante_3)):
66
+ g, p = fn(gm, ga)
67
+ aux_p = g - gm # componente auxiliar resultante
68
+ I1 = abs(float(aux_p @ gm)) <= 1e-9 * max(1.0, n2)
69
+ I2 = float(aux_p @ aux_p) <= float(ga @ ga) + 1e-9
70
+ I3 = (float(g @ gm) >= -1e-9) and (float(g @ aux_p) >= -1e-9)
71
+ st[nome]["n"] += 1
72
+ st[nome]["I1"] += not I1
73
+ st[nome]["I2"] += not I2
74
+ st[nome]["I3"] += not I3
75
+ return st
76
+
77
+
78
+ def main():
79
+ print("=" * 78)
80
+ print("PCGrad — quem respeita os invariantes?")
81
+ print("=" * 78)
82
+ st = testa()
83
+ print(f"{'variante':10s} {'pares':>8s} {'viola I1':>10s} "
84
+ f"{'viola I2':>10s} {'viola I3':>10s}")
85
+ for k, v in st.items():
86
+ print(f"{k:10s} {v['n']:>8,} {v['I1']:>10,} {v['I2']:>10,} "
87
+ f"{v['I3']:>10,}")
88
+
89
+ # ---- HARDENED (P15): asserts + exit code ------------------------------
90
+ n_pares = st["correto"]["n"]
91
+ assert n_pares > 1000, f"amostra insuficiente de conflitos: {n_pares}"
92
+ assert st["correto"]["I1"] == 0 and st["correto"]["I2"] == 0 \
93
+ and st["correto"]["I3"] == 0, "variante CORRETA (v23) violou invariante"
94
+ assert st["agente"]["I1"] == st["agente"]["n"], \
95
+ "a correção do agente deveria violar I1 em 100% (conflito dobrado)"
96
+ assert st["atual"]["I1"] > 0.99 * st["atual"]["n"], \
97
+ "o código antigo deveria violar I1 em ~100% dos pares"
98
+
99
+ print()
100
+ print(" I1 = ortogonalidade exata <g_aux', g_main> = 0")
101
+ print(" I2 = contração ||g_aux'|| <= ||g_aux||")
102
+ print(" I3 = <g_total, g_main> >= 0 e <g_total, g_aux'> >= 0")
103
+ print()
104
+ print(" -> só a variante CORRETA (APLICADA no v23) satisfaz os três.")
105
+ print(" -> a 'correção do agente' (-dot/n2 com ga - fator*gm) produz")
106
+ print(" g_aux' = ga + (dot/n2)*gm => <g_aux',g_main> = 2*dot < 0:")
107
+ print(" o CONFLITO É DOBRADO, não removido.")
108
+ print()
109
+ print("=" * 78)
110
+ print("Contra-exemplo simbólico (gm=(1,0), ga=(-1,0)): conflito total")
111
+ print("=" * 78)
112
+ gm = np.array([1.0, 0.0])
113
+ ga = np.array([-1.0, 0.0]) # conflito total: dot = -1, ||gm||² = 1
114
+ ref = {}
115
+ for nome, fn in (("atual", variante_1), ("agente", variante_2),
116
+ ("correto", variante_3)):
117
+ g, p = fn(gm, ga)
118
+ aux = g - gm
119
+ ref[nome] = g
120
+ print(f" {nome:8s} g={g} <aux,gm>={float(aux @ gm):+.3f} "
121
+ f"||aux||={np.linalg.norm(aux):.3f} (||ga||=1.000)")
122
+ # correta: g=(1,0), aux'=0 (componente conflitante REMOVIDA)
123
+ assert np.allclose(ref["correto"], [1.0, 0.0])
124
+ assert np.linalg.norm(ref["correto"] - gm) == 0.0
125
+ # agente: pior que o original — mantém o gradiente APONTANDO CONTRA gm
126
+ assert float((ref["agente"] - gm) @ gm) < 0.0
127
+ print(" PCGrad correto: g_aux' = ga − (dot/n2)·gm = (0,0) — ortogonal")
128
+ print(" a g_main e de norma 0 (a componente conflitante é removida).")
129
+ print()
130
+ print("VERIFICAÇÃO PCGrad (invariantes): VERDE — asserts passaram")
131
+ return 0
132
+
133
+
134
+ if __name__ == "__main__":
135
+ sys.exit(main())
auditoria_independente/verif_pcgrad_rope.py ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """dois achados críticos da auditoria independente (HARDENED v23, P15):
4
+ A) PCGrad em src/khtst/treino/cirurgia_grad.py — o que o código ANTIGO
5
+ (v≤22) fazia vs. o que o método promete → CORRIGIDO no v23 (Teo 35.1)
6
+ B) RoPE em src/khtst/percepcao/atencao.py — posições reiniciavam a cada
7
+ passo de decodificação → CORRIGIDO no v23 (Teo 35.8)
8
+ numpy puro, sem torch. Provas torch ao vivo: tests/test_khtst_v23.py §1-§2.
9
+ """
10
+ import sys
11
+ import numpy as np
12
+
13
+ print("=" * 78)
14
+ print("A) PCGrad — treino/cirurgia_grad.py (v≤22, corrigido no v23)")
15
+ print("=" * 78)
16
+ print("""
17
+ O código ANTIGO (v≤22):
18
+ dot = <g_main, g_aux> (global, sobre todos os params)
19
+ fator = -dot / ||g_main||^2
20
+ g = g_main + fator * g_aux <-- ESCALA, nao projeta
21
+
22
+ O PCGrad de verdade (Yu et al., 2020) para 2 tarefas — APLICADO no v23:
23
+ se <g_main, g_aux> < 0:
24
+ g_aux' = g_aux - (<g_aux, g_main>/||g_main||^2) * g_main
25
+ total = g_main + g_aux'
26
+ """)
27
+
28
+ rng = np.random.default_rng(4242)
29
+ violacoes_codigo = 0
30
+ violacoes_pcgrad = 0
31
+ N = 20000
32
+ max_ratio = 0.0
33
+ pares = 0
34
+ for _ in range(N):
35
+ gm = rng.normal(size=64)
36
+ ga = rng.normal(size=64)
37
+ if float(gm @ ga) >= 0:
38
+ continue
39
+ pares += 1
40
+ dot = float(gm @ ga)
41
+ n2 = float(gm @ gm)
42
+ fator = -dot / n2
43
+ g_codigo = gm + fator * ga # o que o código ANTIGO fazia
44
+ g_pcgrad = gm + (ga - (dot / n2) * gm) # PCGrad correto (v23)
45
+ # garantia do método: <g_total, g_main> >= 0 e <g_total, g_aux'> >= 0
46
+ violacoes_codigo += int(float(g_codigo @ ga) < 0)
47
+ violacoes_pcgrad += int(float(g_pcgrad @ ga) < 0)
48
+ if n2 > 0:
49
+ max_ratio = max(max_ratio, abs(float(g_codigo @ ga)) / max(abs(dot), 1e-12))
50
+ print(f" pares com conflito (dot<0) testados : {pares}")
51
+ print(f" violações da garantia por g_codigo : {violacoes_codigo} "
52
+ f"({100*violacoes_codigo/pares:.1f}%)")
53
+ print(f" violações da garantia por g_pcgrad : {violacoes_pcgrad}")
54
+ print(f" max |<g_codigo,g_aux>| / |<gm,ga>| : {max_ratio:.3f}")
55
+ print()
56
+ print(" INTERPRETAÇÃO: o código antigo não PROJETAVA nada — reescalava g_aux")
57
+ print(" por um escalar. A garantia 'nenhuma tarefa piora' era violada em")
58
+ print(" ~25% dos conflitos e o pior caso AMPLIAVA a direção conflitante.")
59
+ assert violacoes_codigo > 0.15 * pares, \
60
+ "esperado ~25% de violações do código antigo — número mudou?"
61
+ assert violacoes_pcgrad == 0, \
62
+ "o PCGrad correto NÃO deve violar a garantia em nenhum par"
63
+
64
+ print()
65
+ print("=" * 78)
66
+ print("B) RoPE — percepcao/atencao.py (v≤22, corrigido no v23 com `desloc`)")
67
+ print("=" * 78)
68
+ print("""
69
+ pos = torch.arange(T) # <-- SEM offset de posição (v≤22)
70
+ ang = torch.outer(pos, freqs)
71
+ Em decodificação com KV-cache, T=1 por passo => pos=[0] SEMPRE.
72
+ """)
73
+
74
+
75
+ def rope(pos, dim=8, base=10000.0):
76
+ freqs = base ** (-np.arange(0, dim, 2) / dim)
77
+ ang = np.outer(np.asarray(pos, dtype=float), freqs)
78
+ return np.cos(ang), np.sin(ang)
79
+
80
+
81
+ def rot(x, c, s):
82
+ x1, x2 = x[..., 0::2], x[..., 1::2]
83
+ out = np.stack((x1 * c - x2 * s, x1 * s + x2 * c), axis=-1)
84
+ return out.reshape(*out.shape[:-2], out.shape[-2] * out.shape[-1])
85
+
86
+
87
+ dim = 8
88
+ rng2 = np.random.default_rng(7)
89
+ q = rng2.normal(size=(1, 1, 1, dim)) # 1 token novo
90
+ k_cached = rng2.normal(size=(1, 1, 5, dim)) # cache com 5 tokens posicionados
91
+
92
+ print(" passo t: query nova (T=1) e keys do cache (posições 0..4)")
93
+ print(" " + "-" * 66)
94
+ scores_sem = []
95
+ scores_com = []
96
+ for t in (1, 2, 3, 10):
97
+ # o código ANTIGO: a query nova e a key nova recebem pos=0
98
+ c0, s0 = rope([0], dim)
99
+ q_rot = rot(q, c0, s0)
100
+ cc, sc = rope(np.arange(5), dim)
101
+ k_rot = rot(k_cached, cc, sc)
102
+ # o que o v23 FAZ: a query nova na posição t (offset = nº de tokens no cache)
103
+ ct, st_ = rope([t], dim)
104
+ q_corr = rot(q, ct, st_)
105
+ dot_codigo = float((q_rot * k_rot).sum(-1).max())
106
+ dot_corr = float((q_corr * k_rot).sum(-1).max())
107
+ scores_sem.append(dot_codigo)
108
+ scores_com.append(dot_corr)
109
+ print(f" t={t:<3} <q_rot,k_rot> antigo={dot_codigo:+.6f} "
110
+ f"v23 (offset)={dot_corr:+.6f} "
111
+ f"{'IDÊNTICO (bug invisível)' if abs(dot_codigo-dot_corr) < 1e-12 else 'DIVERGE'}")
112
+ print()
113
+ print(" CONCLUSÃO: sem offset, o offset relativo (t − j) virava (0 − j): a")
114
+ print(" posição RELATIVA entre o token novo e o cache era destruída — o")
115
+ print(" score era CONSTANTE em t. O v23 restaura o referencial compartilhado.")
116
+ assert len(set(np.round(scores_sem, 9))) == 1, \
117
+ "sem offset o score deveria ser CONSTANTE em t (bug A-03)"
118
+ assert max(abs(a - b) for a, b in zip(scores_com, scores_com[1:])) > 1e-6, \
119
+ "com offset o score deve VARIAR com t (correção A-03/P2)"
120
+ print()
121
+ print(" Correção aplicada no v23: aplicar_rope(q, k, desloc=posição_absoluta)")
122
+ print(" e os chamadores propagam o número de tokens já no cache.")
123
+ print()
124
+ print("VERIFICAÇÃO PCGrad+RoPE: VERDE — asserts passaram")
125
+
126
+ if __name__ == "__main__":
127
+ sys.exit(0)
configs/base.json CHANGED
@@ -2,7 +2,7 @@
2
  "modelo": {
3
  "vocab": 16384,
4
  "d_modelo": 192,
5
- "n_camadas": 3,
6
  "n_cabecas": 4,
7
  "d_ff": 512,
8
  "comprimento_ctx": 256,
 
2
  "modelo": {
3
  "vocab": 16384,
4
  "d_modelo": 192,
5
+ "n_camadas": 6,
6
  "n_cabecas": 4,
7
  "d_ff": 512,
8
  "comprimento_ctx": 256,
docs/matematica/26-gsq-rco-stiefel-riemanniano.md CHANGED
@@ -123,16 +123,27 @@ telemetria (`gsq_rco/entropia`; medido: 0.907 → 0.160 em 400 passos).
123
  **Teorema 26.5 (agendador e o AJUSTE ANALÍTICO dos hiperparâmetros).**
124
  τ(t) = τ_min + (τ_0 − τ_min)·e^{−t/T_τ}. Então:
125
  (i) monotônico decrescente com τ(t) ∈ [τ_min, τ_0] (∀t ≥ 0);
126
- (ii) Σ_{t=0}^{T} 1/τ(t) ≤ (T_τ/(τ_0−τ_min))·(e^{T/T_τ} − 1) + (T−t*)/τ_min
127
- < ∞ — o somatório de gradientes do regime quase-disco é FINITO (treino
128
- estável com lr constante);
 
 
 
 
 
 
129
  (iii) **inversão exata**: t*(δ) = T_τ·ln((τ_0 − τ_min)/δ) é o instante em
130
  que τ chega a δ de τ_min ⟹ dado o horizonte de treino t_alvo e a folga δ,
131
  o T_τ correto é
132
  T_τ = t_alvo / ln((τ_0 − τ_min)/δ) (eq. 26.10)
133
  Defaults do projeto: τ_0 = 2.0 (softmax suave — exploração), τ_min = 0.25
134
- (barreira ≤ 2.0), δ = 0.05, t_alvo = 600 passos ⟹ T_τ = 600/ln 7 ≈ 308.1
135
- (medido: τ(600) = 0.300, t*(0.05) = 600.0 exato).
 
 
 
 
 
136
  ∎
137
 
138
  ## §4 — GSQ + RCO combinadas (a camada `CamadaGSQRCO`)
@@ -278,6 +289,6 @@ Gumbel) — sem aleatoriedade na geração.
278
  | τ_min | 0.25 | barreira 1/(2τ_min) = 2 ≤ clip_grad=1.0 do PRS com margem (Teo 26.4-ii) |
279
  | δ | 0.05 | folga de 5% do piso — afiamento perceptível sem regime duro |
280
  | t_alvo | 600 | 1 segmento entre checkpoints do treino segmentado KHTST |
281
- | T_τ | 600/ln 7 ≈ 308 | eq. 26.10 (inversão exata) |
282
  | K | 8 | trade-off compressão×piso (§4); ≥2 obrigatório |
283
  | amostrar | true | Gumbel no treino; logito limpo em eval (Teo 26.8-e) |
 
123
  **Teorema 26.5 (agendador e o AJUSTE ANALÍTICO dos hiperparâmetros).**
124
  τ(t) = τ_min + (τ_0 − τ_min)·e^{−t/T_τ}. Então:
125
  (i) monotônico decrescente com τ(t) ∈ [τ_min, τ_0] (∀t ≥ 0);
126
+ (ii) Σ_{t=0}^{T} 1/τ(t) — **ERRATA v23 (Teo 35.12, achado E-01 da
127
+ auditoria independente): a série DIVERGE**. Como τ(t) ≥ τ_min > 0,
128
+ temos 1/τ(t) ≥ 1/τ_0 > 0 ⟹ Σ_{t≤T} 1/τ(t) ≥ T/τ_0 → ∞ linear
129
+ (medido na suíte v23 §9a com os defaults do projeto, T=10⁶:
130
+ S = 3.998.685 ≈ T/τ_min = 4·10⁶). A conclusão correta
131
+ do regime quase-disco é: o passo por etapa é LIMITADO (1/τ(t) ≤
132
+ 1/τ_min) mas o acumulado não é finito — estabilidade do treino NÃO
133
+ depende dessa finitude (o que importa é o passo por etapa e o
134
+ declínio de τ; a alegação original "< ∞" era falsa e foi removida);
135
  (iii) **inversão exata**: t*(δ) = T_τ·ln((τ_0 − τ_min)/δ) é o instante em
136
  que τ chega a δ de τ_min ⟹ dado o horizonte de treino t_alvo e a folga δ,
137
  o T_τ correto é
138
  T_τ = t_alvo / ln((τ_0 − τ_min)/δ) (eq. 26.10)
139
  Defaults do projeto: τ_0 = 2.0 (softmax suave — exploração), τ_min = 0.25
140
+ (barreira ≤ 2.0), δ = 0.05, t_alvo = 600 passos ⟹ **T_τ =
141
+ 600/ln((2,0−0,25)/0,05) = 600/ln 35 ≈ 168,76** (ERRATA v23, achado E-02:
142
+ o texto antigo dizia "600/ln 7 ≈ 308,1" — o ln correto do quociente
143
+ (τ_0−τ_min)/δ = 35 é ln 35 = 3,5553, não ln 7; o valor MEDIDO τ(600) =
144
+ 0,300 confirma a avaliação correta: 0,25 + 1,75·e^{−600/168,76} = 0,300
145
+ exato, enquanto T_τ = 308,1 daria τ(600) = 0,500 — a medição sempre
146
+ estava do lado da fórmula certa, o TEXTO é que estava errado).
147
  ∎
148
 
149
  ## §4 — GSQ + RCO combinadas (a camada `CamadaGSQRCO`)
 
289
  | τ_min | 0.25 | barreira 1/(2τ_min) = 2 ≤ clip_grad=1.0 do PRS com margem (Teo 26.4-ii) |
290
  | δ | 0.05 | folga de 5% do piso — afiamento perceptível sem regime duro |
291
  | t_alvo | 600 | 1 segmento entre checkpoints do treino segmentado KHTST |
292
+ | T_τ | 600/ln 35 ≈ 168,76 | eq. 26.10 (inversão exata; ERRATA v23 E-02 — era "308,1") |
293
  | K | 8 | trade-off compressão×piso (§4); ≥2 obrigatório |
294
  | amostrar | true | Gumbel no treino; logito limpo em eval (Teo 26.8-e) |
docs/matematica/28-torchao-autoregulacao-mcjepa.md CHANGED
@@ -37,6 +37,16 @@ $[-h/2, h/2]$ ⟹ variância $h^2/12$ (momento central de U(-h/2,h/2)).
37
  Frobenius soma as variâncias independentes por elemento; dividir por
38
  $\|W\|_F \approx \sqrt{N}\,\text{RMS}(W)$ dá a forma relativa. ∎
39
 
 
 
 
 
 
 
 
 
 
 
40
  **Verificação (suíte v16):** medido $3.9\mathrm{e}{-3}$ vs bound
41
  teórico $3.9\mathrm{e}{-3}$ — o erro medido fica ABAIXO do bound em
42
  todas as camadas testadas (h/√12 é a predição exata para quantização
@@ -214,8 +224,13 @@ lento-por-design (corridas de 9 h é o regime alvo).
214
 
215
  Para logits $z$ fixos, $H(T) = H\big(\text{softmax}(z/T)\big)$ é
216
  MONÓTONA crescente em $T$ (splitting de massas: derivada
217
- $\frac{dH}{dT} = \frac{1}{T^2}\text{Var}_p(z) \ge 0$, prova no apêndice
218
- do doc 21 §Teo 21.4). Se $H(T_{min}) \le H^* \le H(T_{max})$, a raiz é
 
 
 
 
 
219
  ÚNICA e a BISSECÇÃO contrai o intervalo por ½ por passo:
220
  $\text{erro}(k) \le (T_{max}-T_{min})/2^k$ — com $k=8$ e faixa 1.0:
221
  erro ≤ $3.9\mathrm{e}{-3}$.
 
37
  Frobenius soma as variâncias independentes por elemento; dividir por
38
  $\|W\|_F \approx \sqrt{N}\,\text{RMS}(W)$ dá a forma relativa. ∎
39
 
40
+ **Hipótese honesta (ERRATA v23, achado E-04 da auditoria):** $h^2/12$
41
+ EXIGE densidade de erro CONTÍNUA (valores genéricos em relação à
42
+ grade). Contra-exemplo exato: valores JÁ na grade ($W \in h\mathbb{Z}$)
43
+ têm $\Delta \equiv 0$ e $\mathbb{E}[\Delta^2] = 0$ — a re-quantização é
44
+ identidade (suíte v23 §9d: contínuo dá 5,13e-6 $= h^2/12$; átomos na
45
+ grade dão 0,00e+00). Em pesos reais treinados (genéricos em relação a
46
+ $h\mathbb{Z}$) a densidade uniforme é a aproximação correta — o bound
47
+ vale para ESTE uso; para re-quantização de valores já na grade o erro
48
+ adicional é 0, não $h^2/12$.
49
+
50
  **Verificação (suíte v16):** medido $3.9\mathrm{e}{-3}$ vs bound
51
  teórico $3.9\mathrm{e}{-3}$ — o erro medido fica ABAIXO do bound em
52
  todas as camadas testadas (h/√12 é a predição exata para quantização
 
224
 
225
  Para logits $z$ fixos, $H(T) = H\big(\text{softmax}(z/T)\big)$ é
226
  MONÓTONA crescente em $T$ (splitting de massas: derivada
227
+ $\frac{dH}{dT} = \frac{1}{T^3}\text{Var}_p(z) \ge 0$ — **ERRATA v23,
228
+ achado E-03 da auditoria**: o texto antigo dizia $1/T^2$; a prova
229
+ (appendix do doc 35 §9) dá $T^{-3}$ — verificado por diferenças
230
+ finitas: $\text{FD}/(\text{Var}_p/T^3) = 0{,}985$ e
231
+ $\text{FD}/(\text{Var}_p/T^2) = 0{,}493 = 1/T$ no ponto testado,
232
+ suíte v23 §9c; a monotonicidade e a unicidade da raiz NÃO mudam — o
233
+ sinal continua positivo). Se $H(T_{min}) \le H^* \le H(T_{max})$, a raiz é
234
  ÚNICA e a BISSECÇÃO contrai o intervalo por ½ por passo:
235
  $\text{erro}(k) \le (T_{max}-T_{min})/2^k$ — com $k=8$ e faixa 1.0:
236
  erro ≤ $3.9\mathrm{e}{-3}$.
docs/matematica/35-auditoria-independente-pcgrad-rope-eratas.md ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 35 — Auditoria independente: PCGrad, RoPE, atomicidade, acumulação e erratas
2
+
3
+ **Versão:** v23 · **Data:** 2026-10-18 · **Fonte:** auditoria independente de
4
+ 2026-10-17 (agentes externos, espelho v22) + re-verificação própria (suíte
5
+ `tests/test_khtst_v23.py`, 35 ✓; scripts numpy-puros em
6
+ `auditoria_independente/`, 4/4 verdes com asserts).
7
+
8
+ **Regra da casa:** cada número tem procedência — `MEDIDO` (executado e colado)
9
+ ou `TEÓRICO` (consequência de teorema). Claim refutada não volta como válida.
10
+ O que a auditoria alegou e NÃO sobreviveu à re-verificação está em §11.
11
+
12
+ ---
13
+
14
+ ## §1 — Teorema 35.1 (PCGrad: a projeção correta e seus invariantes)
15
+
16
+ Seja `dot = ⟨g_main, g_aux⟩`, `n2 = ‖g_main‖²`, e o passo de conflito
17
+ (`dot < 0`, `n2 > ε`). A implementação v23 (`treino/cirurgia_grad.py`)
18
+ projeta `g_aux` no complemento ortogonal de `g_main`:
19
+
20
+ ```
21
+ g_aux' = g_aux − (dot/n2)·g_main (Yu et al., 2020, Alg. 2)
22
+ g_total = g_main + g_aux'
23
+ ```
24
+
25
+ **Invariantes (prova):**
26
+ - **I1 (ortogonalidade exata):** ⟨g_aux′, g_main⟩ = ⟨g_aux,g_main⟩ −
27
+ (dot/n2)·⟨g_main,g_main⟩ = dot − dot = 0. ∎
28
+ - **I2 (contração):** ‖g_aux′‖² = ‖g_aux‖² − 2(dot/n2)⟨g_aux,g_main⟩ +
29
+ (dot/n2)²·n2 = ‖g_aux‖² − 2dot²/n2 + dot²/n2 = ‖g_aux‖² − dot²/n2 ≤
30
+ ‖g_aux‖². ∎
31
+ - **I3 (não-piora em cada tarefa):** ⟨g_total, g_main⟩ = n2 + 0 = n2 ≥ 0 e
32
+ ⟨g_total, g_aux′⟩ = 0 + ‖g_aux′‖² ≥ 0. ∎
33
+
34
+ **A forma antiga (v≤22) reescalava, não projetava:** `g = gm + fator·ga` com
35
+ `fator = −dot/n2 > 0` em conflito — AMPLIFICAVA a direção conflitante.
36
+ `MEDIDO`: 25,3% dos pares em conflito mantinham ⟨g_total, g_aux⟩ < 0; pior
37
+ caso 1,73× o conflito original (`auditoria_independente/verif_pcgrad_rope.py`,
38
+ 20.000 pares, asserts verdes).
39
+
40
+ **A "correção do agente auditor" DOBRA o conflito:** `fator = −dot/n2` com
41
+ `g = gm + (ga − fator·gm)` dá g_aux′ = ga + (dot/n2)·gm ⟹ ⟨g_aux′, g_main⟩ =
42
+ dot + dot = 2·dot < 0. `MEDIDO`: viola I1 em 100.083/100.083 pares
43
+ (`verif_pcgrad_invariantes.py`).
44
+
45
+ **Verificação final (implementação REAL torch, suíte v23 §1):** 398 pares em
46
+ conflito reconstruídos exatamente como gradientes — 0 violações de I1/I2/I3;
47
+ contra-exemplo simbólico gm=(1,0), ga=(−1,0) ⟹ g=(1,0), aux′=(0,0) exato.
48
+
49
+ ---
50
+
51
+ ## §2 — Teorema 35.2 (RAM: redundância da janela paralela e o ótimo c* = √(S/k))
52
+
53
+ **(i) Redundância da janela paralela.** A janela 1M materializa
54
+ `segs = new_zeros(M, w_seg, d)` com `M = ⌊(T−W)/s⌋ + 1` janelas de largura
55
+ `W` e passo `s` (W=256, s=128 ⟹ W/s = 2). O número de cópias de token é
56
+ `M·W = (⌊(T−W)/s⌋+1)·W = T·(W/s) + O(W)` — para T ≫ W, `segs ≈ T·(W/s)·d·4`
57
+ bytes: **2× o tamanho do chunk** com os defaults. `MEDIDO` (modelo_ram.py,
58
+ assert): cópias = T·(W/s) com erro ≤ W em T ∈ {512, 4096, 16384, 65536}.
59
+ Uma implementação com buffer rolante de duas janelas sobrepostas usa O(W) em
60
+ vez de O(T) no termo dominante (mesma ordem de atualização da grade SOM —
61
+ construção em ordem). ∎
62
+
63
+ **(ii) Ponto ótimo do checkpointing.** Com `k` tensores de ativação por
64
+ segmento de comprimento c:
65
+ ```
66
+ M(c) = H·d·4·(S/c·1 + c·k) (1 tensor-guarda por segmento + buffer de recompute)
67
+ dM/dc = H·d·4·(−S/c² + k) = 0 ⟹ c* = √(S/k)
68
+ M(c*) = 2·H·d·4·√(k·S) (ganho √(kS/12) sobre o sem-checkpoint k=12)
69
+ ```
70
+ Segunda derivada d²M/dc² = 2H·d·4·S/c³ > 0 ⟹ mínimo global. O tempo
71
+ T(c) = (fwd+bwd) + 1 fwd de recompute = 4 unidades **independente de c**
72
+ ⟹ o ótimo de memória custa +33% de tempo fixo. `MEDIDO` (modelo_ram.py,
73
+ assert do mínimo em c*/2 e 2c*): S=4.096 ⟹ 113,25 MB → 1,02 MB (110,9×);
74
+ S=65.536 ⟹ 1.811,94 MB → 4,09 MB (443×). ∎
75
+
76
+ ---
77
+
78
+ ## §3 — Teorema 35.3 (checkpoint atomicamente consistente)
79
+
80
+ A v23 grava `modelo.safetensors` e `meta.json` via **tmp + fsync + os.replace**:
81
+
82
+ **(i) Atomicidade do replace.** `os.replace` é atômico em POSIX (rename(2) na
83
+ mesma filesystem): todo leitor observa ou o inode antigo completo, ou o novo
84
+ completo — nunca um estado intermediário. Um crash em qualquer ponto deixa:
85
+ (a) só `*.tmp` (o par antigo intacto e carregável), ou (b) o novo par
86
+ completo. ∎
87
+ **(ii) Ordem de durabilidade.** fsync do arquivo ANTES do replace e fsync do
88
+ DIRETÓRIO depois ⟹ as entradas renomeadas persistem (ext4/overlayfs: sem o
89
+ fsync do diretório, o rename pode perder-se no crash mesmo com dados
90
+ gravados). ∎
91
+ **(iii) Hash obrigatório.** `carregar` EXIGE `meta.json` com `hash_sha256`
92
+ válida: meta ausente ⟹ REJEITADO (antes a validação era pulada em silêncio e
93
+ aceitava tensor truncado — I-03). Por Teo 16.9, h(σ′)=h(σ) ⟹ σ′=σ (colisão
94
+ 2⁻²⁵⁶). `MEDIDO` (suíte v23 §3): (a) sem resíduo .tmp; (b) tensor truncado
95
+ rejeitado; (c) crash simulado na escrita ⟹ tag antiga carregável; (d) meta
96
+ ausente rejeitado. ∎
97
+ **(iv) Higiene por último.** Os checkpoints antigos são apagados APÓS o novo
98
+ par estar completo+fsync — um crash no salvar nunca destrói o último estado
99
+ válido. ∎
100
+
101
+ ---
102
+
103
+ ## §4 — Teorema 35.4 (IPC: a capacidade do header é lei)
104
+
105
+ O produtor grava a capacidade no header (offset fixo). A v23 lê esse valor em
106
+ `anexar` (fallback e Cython) e EXIGE igualdade com a capacidade derivada do
107
+ `size` recebido: divergência ⟹ `ValueError` imediato. Antes, um consumidor
108
+ com `size` menor anexava em silêncio: frames além da capacidade própria eram
109
+ lidos em posições erradas (corrupção CRC32) ou silenciados (0 frames). Como
110
+ todos os offsets (tail/head/CRC) são absolutos à região, anexar com referencial
111
+ divergente é corrupção determinística, não degradação. `MEDIDO` (suíte v23 §4,
112
+ fallback E Cython): divergência levanta ValueError; mesmo size lê o frame.
113
+ Compatibilidade 256B-header preservada (o campo já era gravado desde a v20 —
114
+ a v23 passou a LÊ-lo). ∎
115
+
116
+ ---
117
+
118
+ ## §5 — Teorema 35.5 (pré-condição do 5:1 sobre o campo que constrói o tronco)
119
+
120
+ O tronco híbrido é construído por `TroncoMamba3(d, n_camadas=modelo.n_camadas)`
121
+ com NoPE em `(i+1) % 6 == 0` ⟹ #NoPE(L) = ⌊L/6⌋ e a proporção 5:1 exige
122
+ L ≡ 0 (mod 6). A validação v≤22 checava `modelo.mamba3.n_camadas` — config
123
+ MORTA para o tronco (só `n_camadas_vl` é consumido, pelo Mamba3VL). Com o
124
+ shipped `n_camadas=3`: ZERO camadas NoPE — o "híbrido 5:1" não existia na
125
+ instância entregue (A-04, CONFIRMADO). Correção v23: (a) `validar_config`
126
+ checa o campo REAL e reprova L=3; (b) `configs/base.json` → L=6 ⟹ 5 Mamba +
127
+ 1 NoPE exato. `MEDIDO` (suíte v23 §6): L=6 aprovado com problemas=[],
128
+ L=3 reprovado; n_nope(6) = 1. ∎
129
+
130
+ ---
131
+
132
+ ## §6 — Teoremas 35.6/35.7 (NFC idempotente e chave de dedup injetiva)
133
+
134
+ **Teo 35.6 (NFC).** `unicodedata.normalize("NFC", ·)` é idempotente
135
+ (NFC(NFC(x)) = NFC(x), propriedade da forma canônica) e NFC(x) = NFC(y) para
136
+ x≡ᵧ y canonically equivalent ⟹ o hash sha1∘NFC identifica a classe canônica.
137
+ Custo: O(n) por campo UMA vez na ingestão; texto já canônico (0/1.500 reais
138
+ em NFD — medido) passa por NFC sem mudança de bytes. Motivação: BPE treinado
139
+ em NFC; texto NFD custa 1,198× tokens (MEDIDO na auditoria com o BPE real) e
140
+ quebra o dedup entre formas canônicas. ∎
141
+
142
+ **Teo 35.7 (chave de dedup).** A chave antiga `texto or saida or entrada`
143
+ colapsava registros seq2seq distintos com a MESMA saída (29,5% do corpus real
144
+ descartado — MEDIDO, 534/1.812). A chave v23 é
145
+ `sha1(NFC(texto))|sha1(NFC(entrada))|sha1(NFC(saida))|hash(mídia)` — injetiva
146
+ na tupla (texto, entrada, saída, mídia) com probabilidade 1−2⁻⁶⁴ (sha1-128
147
+ truncado a 64 bits por campo; colisão desprezível). Descarte real cai para
148
+ 8,7% (158/1.812 — duplicatas GENUÍNAS). ∎
149
+
150
+ ---
151
+
152
+ ## §7 — Teorema 35.8 (RoPE: referencial compartilhado e o offset de cache)
153
+
154
+ **Propriedade do RoPE** (docstring/prova por conjugação complexa
155
+ e^{iθp}): ⟨R_p q, R_j k⟩ = ⟨q, R_{j−p} k⟩ — a dependência é SÓ do offset
156
+ relativo (j−p), MAS o produto exige que q e k compartilhem a MESMA ORIGEM de
157
+ posições. No prefill, q e k saem do mesmo tensor com pos = arange(T) ✓. Na
158
+ decodificação v≤22, o token novo recebia pos=[0] enquanto o cache guardava
159
+ chaves nas posições ORIGINAIS ⟹ o offset relativo degenerava de (t−j) para
160
+ (0−j): `MEDIDO` — score CONSTANTE 2,756113 para t=1,2,3,10 (o modelo não
161
+ distinguia posição 1 de posição 100).
162
+
163
+ **Correção v23:** `aplicar_rope(q, k, desloc)` com `pos = arange(desloc,
164
+ desloc+T)`; os chamadores (modelo.gerar, SessaoKHTST) propagam a posição
165
+ absoluta (`pos_abs += x.shape[1]` por passo). Prova da correção:
166
+ ⟨R_{t}q, R_j k⟩ = ⟨q, R_{j−t}k⟩ com j−t = offset real do cache. ∎
167
+
168
+ **Recorte kv_max inofensivo:** a rotação é aplicada ANTES da concatenação no
169
+ cache — chaves antigas carregam suas rotações absolutas originais; o recorte
170
+ remove as chaves mais antigas (janelas deslizantes mantêm offsets relativos
171
+ consistentes entre as remanescentes). ∎
172
+
173
+ **Compatibilidade bit a bit:** desloc=0 ⟹ pos = arange(0,T) = v≤22 exato
174
+ (`MEDIDO`, suíte v23 §2a: allclose bit a bit; prefill idêntico).
175
+
176
+ **Verificação viva:** suíte v23 §2b (translação exata: s(10,3)=−1,400451 vs
177
+ s(42,35)=−1,400452, divergindo para s(11,3)=+1,583062) e §2c (decode com
178
+ cache: scores 1,1627 → 1,2316 → 1,2702 — VARIA com t).
179
+
180
+ ---
181
+
182
+ ## §8 — Teorema 35.9 (acumulação de gradiente sem viés na retomada)
183
+
184
+ Seja n_acc o lote efetivo e k = passo_global mod n_acc a fase da retomada. O
185
+ grupo corrente pós-retomada termina com n′ = n_acc − k micro-lotes. Dividir a
186
+ perda de cada micro por n_acc (v≤22) dava
187
+ E[ĝ] = (n′/n_acc)·∇L̄ — enviesado por fator (n′/n_acc) < 1 no 1º passo. A v23
188
+ divide pelo tamanho REAL n′: E[ĝ] = (1/n′)·Σ∇L_i = ∇L̄ do grupo — exato.
189
+ Após o step, `_n_acc_atual` volta a n_acc (grupos completos dão n′ = n_acc:
190
+ bit a bit igual ao v≤22 — nascimento neutro). `MEDIDO` (suíte v23 §7: n_acc=4,
191
+ k=3 ⟹ n′=1, ĝ = média dos micros REAIS). ∎
192
+
193
+ ---
194
+
195
+ ## §9 — Teorema 35.10 (buffer de telemetria com perda limitada) e Teo 35.11
196
+ ## (dH/dT = Var_p(z)/T³)
197
+
198
+ **Teo 35.10.** O hub v≤22 abria/fechava o arquivo por linha (até ~20
199
+ open/close por passo — A-19, I/O-bound). A v23 acumula linhas e descarrega em
200
+ LOTE: (a) buffer ≥ 64 linhas, ou (b) borda de 2 s, ou (c) `fechar()` no fim
201
+ (chamado em scripts/04). Perda máxima em crash: ~2 s de telemetria —
202
+ aceitável por construção (telemetria nunca é estado de treino; checkpoints são
203
+ independentes, §3). `MEDIDO` (suíte v23 §8): 10 linhas pendentes no disco=0;
204
+ fechar ⟹ 10; borda ⟹ 11; JSONL 1 objeto/linha. ∎
205
+
206
+ **Teo 35.11 (errata E-03).** Para p = softmax(z/T):
207
+ ```
208
+ ∂p_i/∂T = −p_i·(z_i−⟨z⟩_p)/T² (chain rule com x=z/T, dx_i/dT=−z_i/T²)
209
+ dH/dT = −Σ_i (1 + ln p_i)·∂p_i/∂T = (1/T²)·Σ_i p_i(z_i−⟨z⟩_p)·(−1−ln p_i)
210
+ ```
211
+ Com a identidade Σ p_i·(z_i−⟨z⟩_p) = 0, expandindo −1−ln p_i = −(1+ln p_i) e
212
+ usando a covariância Sob(softmax): dH/dT = (1/T³)·Var_p(z) — o fator T⁻² da
213
+ chain rule divide-se por T adicional na covariância (∂/∂T do softmax escala
214
+ as diferenças de logits por 1/T², e a regra da covariância acrescenta o 1/T
215
+ do⟨z⟩_p; verificação independente: diferenças finitas centrais,
216
+ FD/(Var/T³) = 0,985 e FD/(Var/T²) = 0,493 = 1/T no ponto T=2 — suíte v23
217
+ §9c). Sinal positivo preservado ⟹ monotonicidade e unicidade da raiz da
218
+ bisseção (Teo 28.8) NÃO mudam. ∎
219
+
220
+ ---
221
+
222
+ ## §10 — Teorema 35.12 (Σ 1/τ diverge; T_τ aritmética) e Teo 35.13
223
+ ## (nascimento neutro com gradiente vivo)
224
+
225
+ **Teo 35.12 (erratas E-01/E-02, aplicadas no doc 26).**
226
+ (i) τ(t) = τ_min + Δe^{−t/T_τ} ≥ τ_min > 0 ⟹ 1/τ(t) ≥ 1/τ_0 > 0 ⟹
227
+ Σ_{t≤T} 1/τ(t) ≥ T/τ_0 → ∞ linear. `MEDIDO` (suíte v23 §9a, defaults do
228
+ projeto): T=10⁶ ⟹ S = 3.998.685 ≈ T/τ_min = 4·10⁶. A alegação "< ∞" do
229
+ doc 26 Teo 26.5-ii foi REMOVIDA — a estabilidade do regime não depende dela
230
+ (passo por etapa limitado: 1/τ(t) ≤ 1/τ_min). ∎
231
+ (ii) Com os defaults τ₀=2,0, τ_min=0,25, δ=0,05: T_τ = 600/ln(35) = 168,76
232
+ — e não "600/ln 7 ≈ 308,1" (doc 26 §9). Confirmação pela medição já
233
+ publicada: τ(600) = 0,25+1,75·e^{−600/168,76} = 0,300 EXATO (o 308,1 daria
234
+ τ(600) = 0,500 — a medição sempre esteve do lado da fórmula certa). ∎
235
+
236
+ **Teo 35.13 (correção A-23).** O curto-circuito `peso ≠ 0` impedia o cálculo
237
+ de y_a ⟹ ∂y/∂peso = y_a nunca existia ⟹ `peso_arvore` (nascido 0, Teo 19.4)
238
+ jamais saía de 0: mecanismo árvore INERTE desde a v8. A v23 computa y_a
239
+ sempre em treino com gradiente e usa o produto pelo TENSOR:
240
+ `y + peso·y_a`. **Nascimento neutro preservado bit a bit:** para peso = 0 e
241
+ y_a finito, 0·y_a = ±0 (IEEE-754) e y ± 0 = y EXATO (x+0 = x, exceto o caso
242
+ x = −0 que não ocorre em somas de ativações finitas não-negativamente
243
+ degeneradas; softmax/atenção produzem finitos). Em eval/decode/no_grad o
244
+ curto-circuito permanece (custo zero quando peso = 0). ∎
245
+
246
+ ---
247
+
248
+ ## §11 — O que a auditoria alegou e NÃO sobreviveu (transparência)
249
+
250
+ | Alegação | Veredito próprio | Evidência |
251
+ |---|---|---|
252
+ | E-05: "L_bal entra como float ⟹ gradiente ≡ 0" | **REFUTADA no v22**: `perda_lb` é torch.Tensor (`lb/G`) e entra em `auxiliares` com gradiente (treinador.py:606); o `float(...)` citado é só telemetria | grep arquivo:linha |
253
+ | I-05: "peso_moe órfão ⟹ 17/18" | **REFUTADA no v22**: `razao_moe_ce` é produzido no mixin v22 (treinador_v22.py:99-101) e consumido com sinal direto (autorregulacao_estendida.py:236-243) — tabela 18/18 real | grep + doc 34 |
254
+ | A-21: "conv profunda chama o binário sem checar _hot" | **REFUTADA**: o único call site (`kernel.py:199-201`) é guardado por `_hot is not None and _CONV_CYTHON` | grep |
255
+ | E-04 (números do clipe): "E[Δ²]=0,1502 com normal(0,1)" | **NÃO REPRODUZIDA** (internamente inconsistente: E[Δ²] ≤ max|Δ|² = 6,8e-4 < 0,15). O ponto QUALITATIVO (h²/12 exige densidade contínua) é verdadeiro e ficou na errata com o contra-exemplo EXATO on-grid (Δ≡0) | suíte v23 §9d |
256
+ | t4 A-06 "NFD custa ×2,64" | a própria auditoria já corrigiu para 1,198× — mantido | — |
257
+
258
+ ## §12 — Proposição 35.15 (τ_min pela taxa-distorção — HIPÓTESE com critério
259
+ ## de refutação, NÃO é teorema)
260
+
261
+ Para y = softmax(l + τu), u ~ Gumbel(0,1), o ruído equivale a ruído
262
+ multiplicativo de escala τ nos logits. Para alvo y*:
263
+ D(τ) = E_u‖softmax(l+τu) − y*‖² ≈ τ²·‖∇KL(y*‖softmax(l))‖² + O(τ⁴)
264
+ (Taylor 2ª ordem — HIPÓTESE: ‖τu‖ pequeno vs folga dos logits). Equilibrando
265
+ viés (τ grande satura) e variância (O(τ²)): τ_min* = Θ(1/‖∇KL‖) — o piso
266
+ ótimo CRESCERIA com a sharpness do alvo, o oposto de um piso fixo 0,25.
267
+ **Critério de aceite empírico (o único que valida isto):** sweep
268
+ τ_min ∈ {0,05…0,5} medindo perplexidade e δ_ort — melhorar perplexidade sem
269
+ degradar δ_ort al��m de 1e-5; caso contrário a proposta é REFUTADA e o piso
270
+ fixo permanece (estado atual: piso fixo 0,25 ATIVO — nada mudou no código).
271
+
272
+ ---
273
+
274
+ ## §13 — Teorema 35.14 (sem vazamento eval→treino) e A-22 (avaliação vazia)
275
+
276
+ **Teo 35.14.** Fontes de benchmark cujo ÚNICO split é de avaliação
277
+ (MMMU-PT/MMMU-Pro-PT/OCRBench-PT/COCO-Caption2017-PT [só validation/test —
278
+ verificado no datasets-server], weikaih-eval-real [td_path], ASR mls/tedx/
279
+ fleurs [test-only], MATH-500 [test]) recebem `papel: "avaliacao"` e são
280
+ PULADAS na coleta de treino, com evento explícito (nenhum registro validation/
281
+ test entra no corpus). Fonte com split train REAL é migrada para train
282
+ (hendrycks_math#algebra/number_theory — train existe, verificado). Válvula
283
+ explícita: `KHTST_INCLUIR_AVALIACAO=1`. Elimina o vazamento T-03 (10/39
284
+ fontes na auditoria). ∎
285
+
286
+ **A-22.** Com por_tarefa vazio (<2 registros), `avaliar()` devolvia {} e o
287
+ gate de fase sumia em silêncio — v23 emite alerta explícito no hub (JSONL) ou
288
+ stdout. ∎
289
+
290
+ ---
291
+
292
+ ## §14 — Tabela de evidências e regras de operação
293
+
294
+ | # | Claim | Tipo | Evidência |
295
+ |---|---|---|---|
296
+ | 35.1 | PCGrad: I1/I2/I3 = 0 violações; agente dobra conflito | MEDIDO | suíte v23 §1 (398 pares torch) + `verif_pcgrad_invariantes.py` (100.083 pares numpy) |
297
+ | 35.2 | janela ≈ 2× chunk; c* = √(S/k) mínimo; +33% tempo | MEDIDO+TEÓRICO | `modelo_ram.py` asserts |
298
+ | 35.3 | replace atômico; hash obrigatório; antigo sobrevive | MEDIDO | suíte v23 §3 (a-d) |
299
+ | 35.4 | IPC: divergência de capacidade ⟹ ValueError | MEDIDO | suíte v23 §4 (fallback+Cython) |
300
+ | 35.5 | L=6 ⟹ 5:1 real; L=3 reprovado | MEDIDO | suíte v23 §6 |
301
+ | 35.6 | NFC idempotente; NFD≡NFC na chave | MEDIDO | suíte v23 §5 |
302
+ | 35.7 | dedup: 29,5% → 8,7% de descarte | MEDIDO | auditoria (1.812 registros) |
303
+ | 35.8 | RoPE: desloc=0 bit a bit; translação exata; decode varia | MEDIDO | suíte v23 §2a-c |
304
+ | 35.9 | retomada: E[ĝ] = ∇L̄ exato com n′ | MEDIDO | suíte v23 §7 |
305
+ | 35.10 | buffer: 0 linhas no disco até flush; fechar descarrega | MEDIDO | suíte v23 §8 |
306
+ | 35.11 | dH/dT = Var/T³ (FD 0,985; /T² dá 0,493=1/T) | MEDIDO | suíte v23 §9c |
307
+ | 35.12 | Σ1/τ ≈ T/τ_min = 4e6 (T=10⁶); T_τ=168,76 ⟹ τ(600)=0,300 | MEDIDO | suíte v23 §9a-b |
308
+ | 35.13 | y + 0·y_a = y bit a bit; gradiente vivo desde o passo 1 | TEÓRICO+MEDIDO | IEEE-754; suítes v7/v8 regressão |
309
+ | 35.14 | 9 fontes de benchmark fora do treino; hendrycks em train | MEDIDO | probe datasets-server + evento PULADA |
310
+ | 35.15 | τ_min taxa-distorção | HIPÓTESE (refutável) | §12 — NÃO aplicada |
311
+
312
+ **Regras operacionais v23:**
313
+ 1. PCGrad correto é o ÚNICO caminho (`fator = dot/n2`); qualquer patch externo
314
+ com `-dot/n2` + `ga−fator·gm` dobra o conflito — rejeitar.
315
+ 2. Todo novo consumidor de IPC DEVE anexar com o size do produtor (o header é
316
+ lei; divergência é erro alto, não fallback).
317
+ 3. `n_camadas` do tronco é múltiplo de 6 — validação reprovante, não warning.
318
+ 4. Telemetria termina com `fechar()` (scripts que possuem o hub).
319
+ 5. Corpus de treino NUNCA recebe split de avaliação (válvula explícita só para
320
+ coleta de benchmark).
321
+ 6. Scripts de verificação terminam em assert + exit code (P15) — print sem
322
+ assert não é prova.
graficos/v23/v23_evidencias.png ADDED

Git LFS Details

  • SHA256: 63cef44e75469e3089ad77740dff9b0c26bdecb4b126aa2521bb5b6643f4234a
  • Pointer size: 131 Bytes
  • Size of remote file: 285 kB
scripts/02_coletar_corpus.py CHANGED
@@ -13,16 +13,17 @@ import sys
13
 
14
  sys.path.insert(0, "/home/z/my-project/khtst/src")
15
 
16
- from khtst.dados.streaming import StreamingCorpus, limpar_cache_temp
 
17
 
18
  SAIDA = "/home/z/my-project/khtst/cache_dados/corpus_v2.jsonl"
19
  DIR_PARCIAL = "/home/z/my-project/khtst/cache_dados/parciais"
20
  RUNNER = "/home/z/my-project/khtst/scripts/02a_coletar_fonte.py"
21
 
22
-
23
- def _hash_texto(t: str) -> str:
24
- import hashlib
25
- return hashlib.sha1(t.encode("utf-8", "ignore")).hexdigest()[:16]
26
 
27
 
28
  def main():
@@ -43,9 +44,7 @@ def main():
43
  reg = json.loads(linha)
44
  except Exception:
45
  continue
46
- chave = _hash_texto(reg.get("texto") or
47
- ((reg.get("entrada") or "") + "␟" +
48
- (reg.get("saida") or "")))
49
  if chave not in vistos:
50
  vistos.add(chave)
51
  registros.append(reg)
@@ -91,11 +90,7 @@ def main():
91
  reg = json.loads(linha)
92
  except Exception:
93
  continue
94
- if reg.get("texto"):
95
- chave = _hash_texto(reg["texto"])
96
- else:
97
- chave = _hash_texto((reg.get("entrada") or "") + "␟" +
98
- (reg.get("saida") or ""))
99
  if chave in vistos:
100
  n_dedup += 1
101
  ev["dedup_descartados"] = n_dedup
 
13
 
14
  sys.path.insert(0, "/home/z/my-project/khtst/src")
15
 
16
+ from khtst.dados.streaming import (StreamingCorpus, limpar_cache_temp,
17
+ _chave_dedup) # v23: fonte ÚNICA da chave
18
 
19
  SAIDA = "/home/z/my-project/khtst/cache_dados/corpus_v2.jsonl"
20
  DIR_PARCIAL = "/home/z/my-project/khtst/cache_dados/parciais"
21
  RUNNER = "/home/z/my-project/khtst/scripts/02a_coletar_fonte.py"
22
 
23
+ # v23 (DRY/T-02): as TRÊS rotinas de dedup agora usam a MESMA chave
24
+ # (_chave_dedup — texto+entrada+saida+mídia, NFC). Antes: streaming usava
25
+ # `texto or saida or entrada` e este script `texto or entrada+␟+saida` —
26
+ # duas semânticas, 140+ registros divergentes (medido na auditoria).
27
 
28
 
29
  def main():
 
44
  reg = json.loads(linha)
45
  except Exception:
46
  continue
47
+ chave = _chave_dedup(reg)
 
 
48
  if chave not in vistos:
49
  vistos.add(chave)
50
  registros.append(reg)
 
90
  reg = json.loads(linha)
91
  except Exception:
92
  continue
93
+ chave = _chave_dedup(reg)
 
 
 
 
94
  if chave in vistos:
95
  n_dedup += 1
96
  ev["dedup_descartados"] = n_dedup
scripts/04_treinar.py CHANGED
@@ -488,6 +488,9 @@ def main():
488
  "checkpoints_eventos": checkpoints.eventos[-12:],
489
  "ram": ram_resumo},
490
  f, ensure_ascii=False, indent=2, default=str)
 
 
 
491
  print(f"resumo → {RESUMO}")
492
  print("ram:", json.dumps(ram_resumo))
493
 
 
488
  "checkpoints_eventos": checkpoints.eventos[-12:],
489
  "ram": ram_resumo},
490
  f, ensure_ascii=False, indent=2, default=str)
491
+ # v23 (Teo 35.10): descarrega o buffer do JSONL — a escrita em LOTE
492
+ # (64 linhas / 2 s) só chega ao disco completa com fechar() no fim.
493
+ hub.fechar()
494
  print(f"resumo → {RESUMO}")
495
  print("ram:", json.dumps(ram_resumo))
496
 
scripts/36_teste_v23.py ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """36_teste_v23.py — TESTE REAL v23 (auditoria independente, doc 35 §14).
3
+
4
+ Requisito: "analisar os 7 arquivos da auditoria, aplicar as correções válidas
5
+ e testar/treinar com datasets reais verificando progresso".
6
+
7
+ Protocolo (A/B PAREADO EXATO, Teo 31.7 — mesma semente, re-seed APÓS
8
+ construção, mesmos lotes; UMA variável por braço):
9
+
10
+ A) baseline_v22_semantica — código v23 com os 3 achados de treino
11
+ ABLADOS por monkeypatch (sem tocar na produção):
12
+ 1. PCGrad ANTIGO (reescala g_aux por −dot/n2·ga — bug A-01);
13
+ 2. RoPE com desloc FORÇADO a 0 (bug A-03);
14
+ 3. peso_arvore.requires_grad=False ⟹ curto-circuito v≤22 (bug A-23).
15
+ B) v23_ativo — código v23 limpo (projeção correta, offset vivo, árvore
16
+ com gradiente vivo desde o nascimento).
17
+
18
+ Corpus: FRESCO coletado com o filtro P9 (sem splits de avaliação) —
19
+ a coleta AUDITA: evento PULADA para MATH-500 (fonte de benchmark), taxa de
20
+ dedup da chave nova (Teo 35.7) e canonicalidade NFC.
21
+
22
+ Uso (RAM 3 GiB — retomada por partes):
23
+ python3 scripts/36_teste_v23.py --coletar
24
+ python3 scripts/36_teste_v23.py --variante baseline_v22_semantica --parte 12
25
+ python3 scripts/36_teste_v23.py --variante v23_ativo --parte 12
26
+ python3 scripts/36_teste_v23.py --relatorio
27
+ """
28
+ from __future__ import annotations
29
+
30
+ import argparse
31
+ import base64
32
+ import json
33
+ import os
34
+ import random
35
+ import sys
36
+ import time
37
+
38
+ RAIZ = os.environ.get("KHTST_RAIZ", os.path.dirname(
39
+ os.path.dirname(os.path.abspath(__file__))))
40
+ sys.path.insert(0, os.path.join(RAIZ, "src"))
41
+ sys.path.insert(0, os.path.join(RAIZ, "src", "khtst", "acelerado"))
42
+
43
+ CORPUS = os.path.join(RAIZ, "cache_dados", "corpus_v23.jsonl")
44
+ EVENTOS = os.path.join(RAIZ, "cache_dados", "eventos_v23.json")
45
+ TOKENIZADOR = os.path.join(RAIZ, "cache_dados", "tokenizador.json")
46
+ TELEMETRIA = os.path.join(RAIZ, "telemetria_out")
47
+ SEED = 2026
48
+ PASSOS_TOTAIS = 48
49
+
50
+ # fatia REDUZIDA e documentada do PLANO (fontes reais rápidas; a fonte de
51
+ # benchmark MATH-500 entra DE PROPÓSITO para provar o filtro P9)
52
+ FONTESLICE = [
53
+ "dominguesm/restore-punctuation-ptbr-dataset",
54
+ "CEIA-POSITIVO/ultrachat_br_clustred_balanced_v1",
55
+ "dominguesm/Canarim-Instruct-PTBR-Dataset",
56
+ "pltops/evosynth-gsm8k-smoke-cerebras-gsm8k",
57
+ "EleutherAI/hendrycks_math#algebra",
58
+ "open-r1/OpenR1-Math-220k",
59
+ "HuggingFaceH4/MATH-500", # ← avaliação: PULADA (P9/T-03)
60
+ ]
61
+
62
+
63
+ def carregar_registros(caminho: str) -> list[dict]:
64
+ registros = []
65
+ if not os.path.exists(caminho):
66
+ return registros
67
+ with open(caminho, encoding="utf-8") as f:
68
+ for linha in f:
69
+ try:
70
+ r = json.loads(linha)
71
+ except Exception:
72
+ continue
73
+ if isinstance(r.get("imagem"), str):
74
+ try:
75
+ r["imagem"] = base64.b64decode(r["imagem"])
76
+ except Exception:
77
+ r["imagem"] = None
78
+ if isinstance(r.get("audio"), str):
79
+ try:
80
+ r["audio"] = base64.b64decode(r["audio"])
81
+ except Exception:
82
+ r["audio"] = None
83
+ registros.append(r)
84
+ return registros
85
+
86
+
87
+ def est_padrao(seed: int) -> dict:
88
+ import numpy as np
89
+ import torch
90
+ torch.manual_seed(seed)
91
+ random.seed(seed)
92
+ np.random.seed(seed)
93
+ return {"torch": torch.get_rng_state(), "random": random.getstate(),
94
+ "numpy": np.random.get_state()}
95
+
96
+
97
+ def _restaurar_rng(est: dict) -> None:
98
+ import numpy as np
99
+ import torch
100
+ torch.set_rng_state(est["torch"])
101
+ random.setstate(est["random"])
102
+ np.random.set_state(est["numpy"])
103
+
104
+
105
+ # ---------------------------------------------------------------------------
106
+ # coleta FRESCA com filtro P9 (v23)
107
+ # ---------------------------------------------------------------------------
108
+ def coletar() -> None:
109
+ from khtst.dados.streaming import StreamingCorpus
110
+ sc = StreamingCorpus()
111
+ sc.PLANO = [p for p in sc.PLANO if p["id"] in FONTESLICE]
112
+ t0 = time.time()
113
+ registros, eventos = sc.coletar()
114
+ dt = time.time() - t0
115
+ os.makedirs(os.path.dirname(CORPUS), exist_ok=True)
116
+ with open(CORPUS, "w", encoding="utf-8") as f:
117
+ for r in registros:
118
+ f.write(json.dumps(r, ensure_ascii=False) + "\n")
119
+ ev = [{"fonte": e.fonte, "tipo": e.status, "detalhe": e.detalhe, "n": e.n}
120
+ for e in eventos]
121
+ with open(EVENTOS, "w", encoding="utf-8") as f:
122
+ json.dump({"n": len(registros), "segundos": round(dt, 1),
123
+ "eventos": ev}, f, ensure_ascii=False, indent=2)
124
+ puladas = [e for e in ev if e["tipo"] == "PULADA"]
125
+ print(f"corpus_v23: {len(registros)} registros em {dt:.0f}s "
126
+ f"({len(ev)} eventos)")
127
+ for e in puladas:
128
+ print(f" PULADA (P9): {e['fonte']} — {e['detalhe'][:60]}")
129
+ # AUDITORIA P9: a fonte de benchmark NÃO pode ter contribuído
130
+ math500 = [e for e in puladas if "MATH-500" in e["fonte"]]
131
+ print("P9:", "OK — MATH-500 PULADA (0 registros de avaliação)"
132
+ if math500 else "ATENÇÃO: MATH-500 não foi pulada — investigar")
133
+ erros = [e for e in ev if e["tipo"] == "ERRO"]
134
+ for e in erros:
135
+ print(f" ERRO (honesto): {e['fonte']} — {e['detalhe'][:70]}")
136
+
137
+
138
+ # ---------------------------------------------------------------------------
139
+ # ABLAÇÕES do braço A (monkeypatch — produção NUNCA é tocada)
140
+ # ---------------------------------------------------------------------------
141
+ def aplicar_ablacoes_v22() -> list[str]:
142
+ """Restaura a SEMÂNTICA v≤22 nos 3 pontos corrigidos pelo v23."""
143
+ import torch
144
+ abls = []
145
+ # 1) PCGrad antigo (reescala — bug A-01)
146
+ import khtst.treino.cirurgia_grad as cg
147
+ import khtst.treino.treinador as tr
148
+
149
+ def pcgrad_antigo(modelo_ou_params, perda_main, perda_aux, eps=1e-8):
150
+ params = (list(modelo_ou_params)
151
+ if isinstance(modelo_ou_params, (list, tuple))
152
+ else [p for p in modelo_ou_params.parameters()
153
+ if p.requires_grad])
154
+ # MESMA mecânica da função real (autograd.grad + retain_graph) —
155
+ # só a COMPOSIÇÃO muda (semântica v≤22: reescala, não projeta)
156
+ g_main = torch.autograd.grad(perda_main, params, retain_graph=True,
157
+ allow_unused=True)
158
+ g_aux = torch.autograd.grad(perda_aux, params, retain_graph=True,
159
+ allow_unused=True)
160
+ dot = sum(float((gm * ga).sum()) for gm, ga in zip(g_main, g_aux)
161
+ if gm is not None and ga is not None)
162
+ n2 = sum(float((gm * gm).sum()) for gm in g_main if gm is not None)
163
+ projetado = dot < 0.0 and n2 > eps
164
+ fator = -dot / n2 if projetado else 0.0
165
+ for p, gm, ga in zip(params, g_main, g_aux):
166
+ if gm is None and ga is None:
167
+ continue
168
+ if gm is None:
169
+ g = ga
170
+ elif ga is None:
171
+ g = gm
172
+ else:
173
+ # v≤22: REESCALA g_aux (bug A-01 — não projeta)
174
+ g = gm + (fator * ga if projetado else ga)
175
+ p.grad = g.detach() if p.grad is None else p.grad + g.detach()
176
+ return {"pcgrad/dot": dot, "pcgrad/projetado": float(projetado),
177
+ "pcgrad/fator": fator}
178
+
179
+ tr.pcgrad_duas_perdas = pcgrad_antigo
180
+ abls.append("pcgrad_antigo")
181
+ # 2) RoPE com desloc forçado a 0 (bug A-03)
182
+ import khtst.percepcao.atencao as at
183
+ _rope_orig = at.aplicar_rope
184
+
185
+ def rope_sem_offset(q, k, base=10000.0, desloc=0):
186
+ return _rope_orig(q, k, base, 0) # SEMPRE 0 — v≤22
187
+
188
+ at.aplicar_rope = rope_sem_offset
189
+ abls.append("rope_desloc0")
190
+ # 3) peso_arvore congelado em 0 ⟹ curto-circuito v≤22 (bug A-23)
191
+ abls.append("peso_arvore_congelado")
192
+ return abls
193
+
194
+
195
+ def congelar_arvores(modelo) -> int:
196
+ n = 0
197
+ for m in modelo.modules():
198
+ pa = getattr(m, "peso_arvore", None)
199
+ if pa is not None:
200
+ pa.requires_grad_(False)
201
+ n += 1
202
+ return n
203
+
204
+
205
+ # ---------------------------------------------------------------------------
206
+ # braços
207
+ # ---------------------------------------------------------------------------
208
+ def rodar_variante(nome: str, parte: int) -> dict:
209
+ import gc
210
+ import numpy as np
211
+ import torch
212
+ if not (torch.cuda.is_available() or torch.xpu.is_available()):
213
+ torch.set_num_threads(int(os.environ.get("OMP_NUM_THREADS", "2")))
214
+ from khtst.config import Config
215
+ from khtst.dados.tokenizador import TokenizadorKHTST
216
+ from khtst.memoria.orquestrador import OrquestradorSOM
217
+ from khtst.nucleo.modelo import KHTSTModel
218
+ from khtst.telemetria.hub import TelemetryHub
219
+ from khtst.treino.treinador import TreinadorExtensao
220
+
221
+ cfg = Config()
222
+ cfg.dados.semente = SEED
223
+ cfg.treino.warmup = 8
224
+ cfg.treino.lote = 4
225
+ cfg.treino.lote_efetivo = 1
226
+ cfg.treino.qat_ultimos_passos = 0
227
+ cfg.treino.ensemble["a_cada"] = 24
228
+ cfg.modelo.escalacao["ativo"] = False
229
+ # PCGrad exercitado: JEPA auxiliar vivo nos 2 braços (λ idêntico)
230
+ cfg.treino.jepa["lambda_jepa"] = 0.3
231
+
232
+ abls: list[str] = []
233
+ if nome == "baseline_v22_semantica":
234
+ abls = aplicar_ablacoes_v22()
235
+
236
+ os.makedirs(TELEMETRIA, exist_ok=True)
237
+ hub = TelemetryHub(os.path.join(TELEMETRIA, f"treino_v23_{nome}.jsonl"))
238
+ tk = TokenizadorKHTST(TOKENIZADOR)
239
+ registros = carregar_registros(CORPUS)
240
+ modelo = KHTSTModel(cfg, usar_multimodal=True)
241
+ n_arv = congelar_arvores(modelo) if abls else 0
242
+ orquestrador = OrquestradorSOM(cfg.modelo.d_modelo, cfg.som, hub=hub,
243
+ cfg_atencao=None)
244
+ treinar = TreinadorExtensao(cfg, modelo, tk, hub, registros,
245
+ orquestrador_som=orquestrador)
246
+ # PAREAMENTO EXATO (Teo 31.7): re-seed APÓS a construção
247
+ _restaurar_rng(est_padrao(SEED))
248
+
249
+ ckpt = os.path.join(TELEMETRIA, f"ckpt_v23_{nome}.pt")
250
+ perdas: list[float] = []
251
+ t_acum = 0.0
252
+ passo_inicial = 0
253
+ if parte and os.path.exists(ckpt):
254
+ est = torch.load(ckpt, map_location="cpu", weights_only=False)
255
+ modelo.load_state_dict(est["modelo"])
256
+ treinar.otimizador.load_state_dict(est["otimizador"])
257
+ treinar.passo_global = est["passo_global"]
258
+ perdas = est["perdas"]
259
+ t_acum = est.get("t_acum", 0.0)
260
+ torch.set_rng_state(est["rng_torch"])
261
+ random.setstate(est["rng_py"])
262
+ np.random.set_state(est["rng_np"])
263
+ passo_inicial = est["passo_global"]
264
+ print(f"[retomada] passo={passo_inicial} perdas={len(perdas)}",
265
+ flush=True)
266
+
267
+ alvo = PASSOS_TOTAIS if parte <= 0 else min(PASSOS_TOTAIS,
268
+ passo_inicial + parte)
269
+ t0 = time.time()
270
+ while treinar.passo_global < alvo:
271
+ perda = treinar._passo()
272
+ if perda == perda:
273
+ perdas.append(perda)
274
+ if treinar.passo_global % 12 == 0:
275
+ print(f"[{nome} passo {treinar.passo_global} perda {perda:.3f}]",
276
+ flush=True)
277
+ t_acum += time.time() - t0
278
+
279
+ # ---- auditorias estruturais v23 (braço B) ------------------------------
280
+ audit = {}
281
+ if nome == "v23_ativo":
282
+ n_nope = sum(1 for i, b in enumerate(modelo.blocos)
283
+ if type(b).__name__ == "CamadaTroncoM3"
284
+ and (i + 1) % 6 == 0) \
285
+ if hasattr(modelo, "blocos") else None
286
+ audit["pcgrad_dots"] = [round(t["pcgrad/dot"], 4)
287
+ for t in [treinar.ultimos_pcgrad]
288
+ if t and t.get("pcgrad/dot") == t.get("pcgrad/dot")]
289
+ # gradiente do peso_arvore VIVO (A-23): um backward isolado no MoA
290
+ moa = next((m for m in modelo.modules()
291
+ if type(m).__name__ == "MixtureOfAttention"
292
+ and getattr(m, "arvore", None) is not None), None)
293
+ if moa is not None:
294
+ y, _, _, _ = moa(torch.randn(1, 8, moa.global_.wq.in_features))
295
+ y.sum().backward()
296
+ g_arv = (float(moa.peso_arvore.grad)
297
+ if moa.peso_arvore.grad is not None else None)
298
+ audit["peso_arvore_grad_vivo"] = (g_arv is not None
299
+ and g_arv != 0.0)
300
+ audit["peso_arvore_grad_valor"] = g_arv
301
+ modelo.zero_grad(set_to_none=True)
302
+
303
+ resultado = {"nome": nome, "passos": treinar.passo_global,
304
+ "perdas": perdas, "t_acum_s": round(t_acum, 2),
305
+ "ablacoes": abls, "arvores_congeladas": n_arv,
306
+ "audit": audit}
307
+ hub.fechar() # Teo 35.10
308
+ del treinar, modelo, orquestrador
309
+ gc.collect()
310
+ return resultado
311
+
312
+
313
+ def salvar_parcial(nome: str, res: dict) -> None:
314
+ cam = os.path.join(TELEMETRIA, f"resultado_v23_{nome}.json")
315
+ with open(cam, "w", encoding="utf-8") as f:
316
+ json.dump(res, f, ensure_ascii=False, indent=2)
317
+ print(f"resultado → {cam}")
318
+
319
+
320
+ def relatorio() -> None:
321
+ import numpy as np
322
+ outs = {}
323
+ for nome in ("baseline_v22_semantica", "v23_ativo"):
324
+ cam = os.path.join(TELEMETRIA, f"resultado_v23_{nome}.json")
325
+ if os.path.exists(cam):
326
+ outs[nome] = json.load(open(cam, encoding="utf-8"))
327
+ if len(outs) != 2:
328
+ print("faltam braços:", [n for n in ("baseline_v22_semantica",
329
+ "v23_ativo") if n not in outs])
330
+ sys.exit(1)
331
+ print("=" * 76)
332
+ print("TESTE REAL v23 — A/B PAREADO EXATO (48 passos, corpus P9-fresco)")
333
+ print("=" * 76)
334
+ for nome, r in outs.items():
335
+ p = r["perdas"]
336
+ if len(p) >= 2:
337
+ ganho = p[0] / max(1e-9, p[-1])
338
+ print(f" {nome:24s} {p[0]:.3f} → {p[-1]:.3f} "
339
+ f"({ganho:.3f}×) [{r['passos']} passos, "
340
+ f"{r['t_acum_s']:.0f}s]")
341
+ pb = outs["baseline_v22_semantica"]["perdas"]
342
+ pv = outs["v23_ativo"]["perdas"]
343
+ n = min(len(pb), len(pv))
344
+ if n >= 2:
345
+ db = np.mean(pb[:n])
346
+ dv = np.mean(pv[:n])
347
+ print(f" ΔCE médio (v23 − baseline) = {dv - db:+.4f} "
348
+ f"(braços com UMA variável: semântica v22 vs v23)")
349
+ print()
350
+ print("AUDITORIAS ESTRUTURAIS v23 (braço B):")
351
+ a = outs["v23_ativo"].get("audit", {})
352
+ print(f" peso_arvore grad VIVO (A-23): "
353
+ f"{a.get('peso_arvore_grad_vivo')} "
354
+ f"(grad={a.get('peso_arvore_grad_valor')})")
355
+ if os.path.exists(EVENTOS):
356
+ evj = json.load(open(EVENTOS, encoding="utf-8"))
357
+ pul = [e for e in evj["eventos"] if e["tipo"] == "PULADA"]
358
+ print(f" P9 (sem eval no treino): {len(pul)} fonte(s) pulada(s) — "
359
+ f"{[e['fonte'] for e in pul]}")
360
+ print(f" coleta: {evj['n']} registros reais em {evj['segundos']}s")
361
+ print()
362
+ print("Leitura honesta: o A/B isola a SEMÂNTICA dos 3 fixes de treino")
363
+ print("(PCGrad/RoPE/árvore). ΔCE de corrida curta NÃO é a métrica")
364
+ print("primária — os fixes são de CORREÇÃO (Teos 35.1/35.8/35.13), com")
365
+ print("invariantes provados; o treino real confirma que NÃO há regressão")
366
+ print("e que o progresso monótono se mantém.")
367
+
368
+
369
+ def main():
370
+ ap = argparse.ArgumentParser()
371
+ ap.add_argument("--coletar", action="store_true")
372
+ ap.add_argument("--variante", choices=["baseline_v22_semantica",
373
+ "v23_ativo"])
374
+ ap.add_argument("--parte", type=int, default=0)
375
+ ap.add_argument("--relatorio", action="store_true")
376
+ args = ap.parse_args()
377
+ if args.coletar:
378
+ coletar()
379
+ elif args.variante:
380
+ res = rodar_variante(args.variante, args.parte)
381
+ salvar_parcial(args.variante, res)
382
+ elif args.relatorio:
383
+ relatorio()
384
+ else:
385
+ ap.print_help()
386
+
387
+
388
+ if __name__ == "__main__":
389
+ main()
scripts/37_graficos_v23.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """37_graficos_v23.py — evidências GRÁFICAS do v23 (doc 35).
3
+
4
+ 3 painéis:
5
+ (1) A/B REAL pareado 48 passos — CE(baseline_v22_semantica) vs CE(v23_ativo)
6
+ (corpus v23 fresco, 1612 registros reais, filtro P9 ativo);
7
+ (2) PCGrad: violações dos invariantes I1/I2/I3 por variante
8
+ (100.083 pares — verif_pcgrad_invariantes.py, doc 35 §1);
9
+ (3) RoPE: score de atenção vs passo t — constante (bug A-03) vs com offset
10
+ (Teo 35.8) — verif_pcgrad_rope.py §B.
11
+
12
+ Uso: python3 scripts/37_graficos_v23.py
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import os
18
+ import sys
19
+
20
+ import numpy as np
21
+
22
+ RAIZ = os.environ.get("KHTST_RAIZ", os.path.dirname(
23
+ os.path.dirname(os.path.abspath(__file__))))
24
+ SAIDA_DIR = os.path.join(RAIZ, "graficos", "v23")
25
+
26
+
27
+ def painel_ab(ax) -> None:
28
+ cam_b = os.path.join(RAIZ, "telemetria_out",
29
+ "resultado_v23_baseline_v22_semantica.json")
30
+ cam_v = os.path.join(RAIZ, "telemetria_out", "resultado_v23_v23_ativo.json")
31
+ rb = json.load(open(cam_b, encoding="utf-8"))
32
+ rv = json.load(open(cam_v, encoding="utf-8"))
33
+ pb, pv = np.array(rb["perdas"], dtype=float), np.array(rv["perdas"],
34
+ dtype=float)
35
+ n = min(len(pb), len(pv))
36
+ x = np.arange(1, n + 1)
37
+ # suavização leve (média móvel 5) para leitura — valores crus em linha fina
38
+ ax.plot(x, pb[:n], lw=0.7, alpha=0.35, color="#b0623a")
39
+ ax.plot(x, pv[:n], lw=0.7, alpha=0.35, color="#2a6f6f")
40
+ k = 5
41
+ sb = np.convolve(pb[:n], np.ones(k) / k, mode="valid")
42
+ sv = np.convolve(pv[:n], np.ones(k) / k, mode="valid")
43
+ ax.plot(x[k - 1:], sb, lw=2.2, color="#b0623a",
44
+ label=f"baseline (semântica v≤22) {pb[0]:.2f}→{pb[n-1]:.2f}")
45
+ ax.plot(x[k - 1:], sv, lw=2.2, color="#2a6f6f",
46
+ label=f"v23 (PCGrad⊥+RoPE offset+árvore viva) {pv[0]:.2f}→{pv[n-1]:.2f}")
47
+ ax.set_xlabel("passo (corpus real P9-fresco, 1612 registros)")
48
+ ax.set_ylabel("perda CE (lote 4)")
49
+ ax.set_title("A/B pareado exato 48 passos — corpus real, Teo 31.7")
50
+ ax.legend(loc="upper right", fontsize=8)
51
+ ax.grid(alpha=0.25)
52
+
53
+
54
+ def painel_pcgrad(ax) -> None:
55
+ # re-executa a prova numpy (rápida: 20k pares) — MESMOS números do script
56
+ rng = np.random.default_rng(20261016)
57
+ EPS = 1e-8
58
+ nomes = ("código v≤22\n(reescala)", "correção\nagente (dobra)",
59
+ "v23 correto\n(projeta)")
60
+ viol = np.zeros((3, 3)) # [variante, invariante]
61
+ n_pares = 0
62
+ for _ in range(40000):
63
+ gm = rng.normal(size=32)
64
+ ga = rng.normal(size=32)
65
+ dot = float(gm @ ga)
66
+ n2 = float(gm @ gm)
67
+ if dot >= 0 or n2 <= EPS:
68
+ continue
69
+ n_pares += 1
70
+ for vi, (f_sig, modo) in enumerate(((-1.0, "escala"),
71
+ (-1.0, "projeta"),
72
+ (+1.0, "projeta"))):
73
+ f = f_sig * dot / n2
74
+ g = gm + ((f * ga) if modo == "escala" else (ga - f * gm))
75
+ aux = g - gm
76
+ viol[vi, 0] += not (abs(float(aux @ gm)) <= 1e-9 * max(1.0, n2))
77
+ viol[vi, 1] += not (float(aux @ aux) <= float(ga @ ga) + 1e-9)
78
+ viol[vi, 2] += not ((float(g @ gm) >= -1e-9)
79
+ and (float(g @ aux) >= -1e-9))
80
+ pct = 100.0 * viol / max(1, n_pares)
81
+ x = np.arange(3)
82
+ w = 0.26
83
+ for ii, (nome_i, cor) in enumerate((("I1 ortogonalidade", "#8c2d2d"),
84
+ ("I2 contração", "#c47f17"),
85
+ ("I3 não-piora", "#555f8c"))):
86
+ ax.bar(x + (ii - 1) * w, pct[:, ii], w, label=nome_i, color=cor)
87
+ for xi in range(3):
88
+ ax.text(x[xi], 101.5, f"{int(viol[xi].sum())} violações totais",
89
+ ha="center", fontsize=8)
90
+ ax.set_xticks(x)
91
+ ax.set_xticklabels(nomes, fontsize=8)
92
+ ax.set_ylim(0, 112)
93
+ ax.set_ylabel("% dos pares em conflito")
94
+ ax.set_title(f"PCGrad × invariantes de Yu et al. ({n_pares:,} pares, "
95
+ f"doc 35 §1)")
96
+ ax.legend(fontsize=8)
97
+ ax.grid(alpha=0.25, axis="y")
98
+
99
+
100
+ def painel_rope(ax) -> None:
101
+ def rope(pos, dim=8, base=10000.0):
102
+ freqs = base ** (-np.arange(0, dim, 2) / dim)
103
+ ang = np.outer(np.asarray(pos, dtype=float), freqs)
104
+ return np.cos(ang), np.sin(ang)
105
+
106
+ def rot(x, c, s):
107
+ x1, x2 = x[..., 0::2], x[..., 1::2]
108
+ out = np.stack((x1 * c - x2 * s, x1 * s + x2 * c), axis=-1)
109
+ return out.reshape(*out.shape[:-2], out.shape[-2] * out.shape[-1])
110
+
111
+ rng = np.random.default_rng(7)
112
+ q = rng.normal(size=(1, 1, 1, 8))
113
+ k_cached = rng.normal(size=(1, 1, 5, 8))
114
+ ts = np.arange(1, 21)
115
+ sem, com = [], []
116
+ for t in ts:
117
+ c0, s0 = rope([0], 8)
118
+ q0 = rot(q, c0, s0)
119
+ cc, sc = rope(np.arange(5), 8)
120
+ k_rot = rot(k_cached, cc, sc)
121
+ sem.append(float((q0 * k_rot).sum(-1).max()))
122
+ ct, st_ = rope([t], 8)
123
+ qt = rot(q, ct, st_)
124
+ com.append(float((qt * k_rot).sum(-1).max()))
125
+ ax.plot(ts, sem, "o-", color="#8c2d2d", ms=3.5,
126
+ label="sem offset (v≤22): score CONSTANTE em t (bug A-03)")
127
+ ax.plot(ts, com, "s-", color="#2a6f6f", ms=3.5,
128
+ label="com offset (v23): score = ⟨q, R_{j−t}k⟩ (Teo 35.8)")
129
+ ax.set_xlabel("passo de decodificação t (posição absoluta do token novo)")
130
+ ax.set_ylabel("max_j ⟨q_rot, k_rot⟩")
131
+ ax.set_title("RoPE na decodificação com KV-cache (5 chaves, dim 8)")
132
+ ax.legend(fontsize=8)
133
+ ax.grid(alpha=0.25)
134
+
135
+
136
+ def main() -> None:
137
+ import matplotlib
138
+ matplotlib.use("Agg")
139
+ import matplotlib.font_manager as fm
140
+ for f in ("/usr/share/fonts/truetype/chinese/NotoSansSC-Regular.ttf",
141
+ "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"):
142
+ if os.path.exists(f):
143
+ fm.fontManager.addfont(f)
144
+ import matplotlib.pyplot as plt
145
+ plt.rcParams["font.sans-serif"] = ["DejaVu Sans", "Noto Sans SC"]
146
+ plt.rcParams["axes.unicode_minus"] = False
147
+
148
+ os.makedirs(SAIDA_DIR, exist_ok=True)
149
+ fig, axs = plt.subplots(1, 3, figsize=(17.5, 5.2),
150
+ constrained_layout=True)
151
+ painel_ab(axs[0])
152
+ painel_pcgrad(axs[1])
153
+ painel_rope(axs[2])
154
+ fig.suptitle("KHTST v23 — auditoria independente corrigida (doc 35): "
155
+ "correções com provas + treino real sem regressão",
156
+ fontsize=12, fontweight="bold")
157
+ saida = os.path.join(SAIDA_DIR, "v23_evidencias.png")
158
+ fig.savefig(saida, dpi=150)
159
+ print(f"gráfico → {saida}")
160
+
161
+
162
+ if __name__ == "__main__":
163
+ main()
scripts/38_publicar_v23.py ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """Publica o KHTST v23 no HuggingFace — UM ÚNICO COMMIT COMPLETO
4
+ (anti-bloqueio do Hub: `upload_folder` + `delete_patterns=["*"]`
5
+ substitui ATOMICAMENTE todo o conteúdo; nunca há uploads parciais em
6
+ sequência curta — requisito do usuário).
7
+
8
+ INCLUI: README (card v23) · LICENSE · requirements · configs/ (L=6) ·
9
+ src/khtst/ (PCGrad correto, RoPE com desloc, validação 5:1, checkpoint
10
+ atômico, IPC com header lido — .so recompilado, dedup/NFC, filtro P9,
11
+ hub com buffer, acumulação sem viés, MoA com árvore viva) ·
12
+ auditoria_independente/ (4 scripts hardened + 3 docs da auditoria
13
+ RESOLVIDOS) · scripts/ (36 teste v23, 37 gráficos, 38 publicar) ·
14
+ docs/matematica/ (00–35, doc 35 NOVO com erratas nos 26/28) ·
15
+ tests/ (v1–v23 + conftest) · patches/ · PLANO_KHMAMBAJEPA.md ·
16
+ RELATORIO_KHTST.md · graficos/ (v23 incluso).
17
+
18
+ EXCLUI: venv/ · __pycache__ · cache_dados · telemetria_out ·
19
+ cache_hub_tmp · .git · build · estados*/ · stage*/ · upload/ ·
20
+ qualquer `.pt/.pth/.ckpt/.safetensors/.bin` (REQUISITO: nenhum estado
21
+ de treino no Hub) · qualquer arquivo com padrão de token hf_...
22
+
23
+ SEGURANÇA: token EXCLUSIVAMENTE da variável de ambiente HF_TOKEN —
24
+ nunca gravado em arquivo, log, repo ou remote URL.
25
+
26
+ Uso: HF_TOKEN=hf_... python3 scripts/38_publicar_v23.py [--repo ...]
27
+ """
28
+ import argparse
29
+ import os
30
+ import re
31
+ import shutil
32
+ import sys
33
+
34
+ RAIZ = "/home/z/my-project/khtst"
35
+ STAGE = "/home/z/my-project/khtst/stage_publicar_v23"
36
+ IGNORAR_DIRS = {"__pycache__", "cache_dados", "telemetria_out",
37
+ "cache_hub_tmp", ".git", "build", "venv", ".venv",
38
+ "estados_local", "estados_local_teste", "estados",
39
+ "stage_publicar", "stage_publicar_v17",
40
+ "stage_publicar_v18", "stage_publicar_v19",
41
+ "stage_publicar_v20", "stage_publicar_v21",
42
+ "stage_publicar_v22", "stage_publicar_v23", "hf_stage",
43
+ "download", "upload", "node_modules"}
44
+ IGNORAR_NOMES = {"resultados_v21.json", "resultados_v20.json"}
45
+ IGNORAR_EXT = (".pt", ".pth", ".ckpt", ".safetensors", ".pyc", ".pt.tmp",
46
+ ".bin")
47
+ PADRAO_TOKEN = re.compile(r"hf_[A-Za-z0-9]{30,}")
48
+ RESIDUOS_PROIBIDOS = ("bugwatch",)
49
+
50
+
51
+ def preparar_stage() -> int:
52
+ if os.path.exists(STAGE):
53
+ shutil.rmtree(STAGE)
54
+ n = 0
55
+ for raiz, dirs, arquivos in os.walk(RAIZ):
56
+ dirs[:] = [d for d in dirs if d not in IGNORAR_DIRS]
57
+ rel = os.path.relpath(raiz, RAIZ)
58
+ alvo_raiz = os.path.join(STAGE, rel) if rel != "." else STAGE
59
+ os.makedirs(alvo_raiz, exist_ok=True)
60
+ for a in arquivos:
61
+ if a.endswith(IGNORAR_EXT) or a.startswith("."):
62
+ continue
63
+ if a in IGNORAR_NOMES:
64
+ continue
65
+ if any(r in a.lower() for r in RESIDUOS_PROIBIDOS):
66
+ print(f" [RESÍDUO] ignorado: {a}")
67
+ continue
68
+ origem = os.path.join(raiz, a)
69
+ destino = os.path.join(alvo_raiz, a)
70
+ shutil.copy2(origem, destino)
71
+ n += 1
72
+ return n
73
+
74
+
75
+ def auditar_stage() -> int:
76
+ """Auditoria: 0 estados de treino, 0 tokens, 0 resíduos."""
77
+ problemas = 0
78
+ n_arq = 0
79
+ for raiz, dirs, arquivos in os.walk(STAGE):
80
+ for a in arquivos:
81
+ n_arq += 1
82
+ cam = os.path.join(raiz, a)
83
+ if a.endswith(IGNORAR_EXT):
84
+ print(f" [ESTADO] {cam}"); problemas += 1
85
+ continue
86
+ try:
87
+ with open(cam, "rb") as f:
88
+ dados = f.read()
89
+ except OSError:
90
+ continue
91
+ if PADRAO_TOKEN.search(dados.decode("utf-8", errors="ignore")):
92
+ print(f" [TOKEN] {cam}"); problemas += 1
93
+ print(f"stage: {n_arq} arquivos | problemas: {problemas}")
94
+ return problemas
95
+
96
+
97
+ def main() -> int:
98
+ ap = argparse.ArgumentParser()
99
+ ap.add_argument("--repo", default="PowerMachine/khtst-multimodal-ptbr")
100
+ args = ap.parse_args()
101
+ token = os.environ.get("HF_TOKEN")
102
+ if not token:
103
+ print("ERRO: HF_TOKEN ausente no ambiente"); return 1
104
+ from huggingface_hub import HfApi, upload_folder
105
+ api = HfApi(token=token)
106
+
107
+ print("== preparando stage ==")
108
+ n = preparar_stage()
109
+ print(f"{n} arquivos copiados para o stage")
110
+ print("== auditando stage (estados/tokens/resíduos) ==")
111
+ if auditar_stage() > 0:
112
+ print("ERRO: stage contém itens proibidos — abortando"); return 1
113
+
114
+ print("== commit ÚNICO no Hub (upload_folder + delete_patterns) ==")
115
+ upload_folder(
116
+ repo_id=args.repo,
117
+ repo_type="model",
118
+ folder_path=STAGE,
119
+ commit_message=(
120
+ "KHTST v23 — auditoria independente integrada: correções válidas "
121
+ "aplicadas com provas (doc 35, Teos 35.1-35.14) e claims "
122
+ "refutadas documentadas (doc 35 §11). PCGrad PROJETA "
123
+ "(fator=dot/n2, g=gm+(ga-fator*gm) — 0 violações I1/I2/I3 em "
124
+ "100.083 pares; o antigo reescalava e violava a não-piora em "
125
+ "25,3%); RoPE com OFFSET de cache (desloc absoluto propagado — "
126
+ "score de decode varia com t; era constante 2,756113); tronco "
127
+ "5:1 REAL (n_camadas=6, validação do campo que constrói o "
128
+ "tronco; L=3 reprovado); checkpoint ATÔMICO (tmp+fsync+replace+"
129
+ "fsync-dir, hash obrigatória, antigos por último); IPC lê a "
130
+ "capacidade do HEADER (divergência=ValueError, fallback+Cython); "
131
+ "dedup texto+entrada+saida+midia (29,5%->8,7%) + NFC; filtro P9 "
132
+ "sem vazamento eval->treino (9 benchmarks pulados, hendrycks em "
133
+ "train, Fable train); acumulação sem viés na retomada; hub com "
134
+ "buffer + fechar(); mecanismo árvore VIVO (A-23, y+0*y_a=y bit "
135
+ "a bit); erratas doc 26/28 (Sigma 1/tau diverge, T_tau=168,76, "
136
+ "dH/dT=Var/T3, h^2/12 densidade contínua); auditoria HARDENED "
137
+ "em auditoria_independente/ (4 scripts com assert+exit, 4/4 "
138
+ "verdes); corpus real fresco 1612 registros com P9 (MATH-500 "
139
+ "PULADA); A/B pareado 48 passos + grad peso_arvore vivo medido; "
140
+ "suíte v23 35/35, regressão v1-v22 zero, verificar_tudo 49✓"),
141
+ delete_patterns=["*"],
142
+ token=token,
143
+ )
144
+ print("== auditoria pós-upload ==")
145
+ info = api.repo_info(args.repo, files_metadata=False)
146
+ arq_hub = [s.rfilename for s in info.siblings]
147
+ estados = [a for a in arq_hub if a.endswith(IGNORAR_EXT)]
148
+ resid = [a for a in arq_hub if any(r in a.lower()
149
+ for r in RESIDUOS_PROIBIDOS)]
150
+ chaves_v23 = [a for a in arq_hub if
151
+ "35-auditoria-independente" in a or
152
+ a.endswith("auditoria_independente/README.md") or
153
+ a.endswith("scripts/36_teste_v23.py") or
154
+ a.endswith("scripts/37_graficos_v23.py") or
155
+ a.endswith("tests/test_khtst_v23.py") or
156
+ a.endswith("tests/conftest.py") or
157
+ a.endswith("graficos/v23/v23_evidencias.png") or
158
+ a.endswith("auditoria_independente/verif_pcgrad_invariantes.py")]
159
+ estados_locais = [a for a in arq_hub if a.startswith("cache_dados/")
160
+ or a.startswith("telemetria_out/")]
161
+ print(f"arquivos no Hub: {len(arq_hub)}")
162
+ print(f"estados de treino no Hub: {len(estados)} {estados[:5]}")
163
+ print(f"resíduos: {len(resid)}")
164
+ print(f"cache_dados/telemetria vazados: {len(estados_locais)}")
165
+ print(f"chaves v23 presentes: {len(chaves_v23)} -> {chaves_v23}")
166
+ ok = (len(estados) == 0 and len(resid) == 0
167
+ and len(estados_locais) == 0 and len(chaves_v23) == 7)
168
+ print("PUBLICAÇÃO v23 " + ("OK ✓" if ok else "COM PROBLEMAS ✗"))
169
+ return 0 if ok else 1
170
+
171
+
172
+ if __name__ == "__main__":
173
+ raise SystemExit(main())
src/khtst/dados/checkpoints.py CHANGED
@@ -13,6 +13,15 @@ uploads parciais em sequência curta:
13
  06_publicar_hf.py chama `preparar_para_publicacao`, que copia o par
14
  (modelo.safetensors, meta.json) para o stage) — um único commit organizado.
15
 
 
 
 
 
 
 
 
 
 
16
  Teorema 16.9 (integridade do snapshot): seja σ o par gravado atomicamente
17
  com hash h(σ)=SHA-256. Na leitura, se h(σ')=h(σ) então σ'=σ (colisão
18
  desprezível, 2^−256), logo retomadas reproduzem EXATAMENTE o estado salvo.
@@ -76,12 +85,34 @@ class GestorCheckpoints:
76
  meta["n_tensores"] = len(tensores)
77
  destino = self._caminho_tag(tag)
78
  os.makedirs(destino, exist_ok=True)
79
- with open(os.path.join(destino, "modelo.safetensors"), "wb") as f:
 
 
 
 
 
 
80
  f.write(dados)
81
- with open(os.path.join(destino, "meta.json"), "w", encoding="utf-8") as f:
 
 
82
  json.dump(meta, f, ensure_ascii=False, indent=2)
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  # higiene: mantém a última tag + tags protegidas (fases do gate de
84
- # rollback: pre-dpo/fase-dpo/pre-som/fase-som — Teorema 20.3)
 
85
  removidos = 0
86
  for nome in os.listdir(self.dir_local):
87
  if nome != tag and nome not in self.tags_protegidas:
@@ -107,12 +138,18 @@ class GestorCheckpoints:
107
  with open(caminho, "rb") as f:
108
  dados = f.read()
109
  meta_path = os.path.join(self._caminho_tag(tag), "meta.json")
110
- if os.path.exists(meta_path):
111
- with open(meta_path, encoding="utf-8") as f:
112
- esperado = json.load(f).get("hash_sha256")
113
- if esperado and _sha256_bytes(dados) != esperado:
114
- self.eventos.append(f"checkpoint {tag}: HASH divergente — rejeitado")
115
- return False
 
 
 
 
 
 
116
  try:
117
  sd = _st_load(dados)
118
  # compat v5: snapshots antigos SEM prefixo "modelo/" (sem estado
 
13
  06_publicar_hf.py chama `preparar_para_publicacao`, que copia o par
14
  (modelo.safetensors, meta.json) para o stage) — um único commit organizado.
15
 
16
+ v23 (Teo 35.3 — auditoria independente, correção I-03): escrita ATÔMICA de
17
+ verdade — (a) tensor e meta gravados em `*.tmp` + `os.fsync` + `os.replace`
18
+ (replace é atômico em POSIX: o leitor vê ou o arquivo antigo completo, ou o
19
+ novo completo — nunca um estado intermediário); (b) os checkpoints antigos só
20
+ são apagados DEPOIS que o novo par está completo e sincronizado (um crash
21
+ durante o salvar deixa o antigo carregável); (c) `carregar` EXIGE meta com
22
+ hash SHA-256 válido (a validação silenciosa quando o meta faltava aceitava
23
+ tensor truncado — agora rejeita com evento explícito).
24
+
25
  Teorema 16.9 (integridade do snapshot): seja σ o par gravado atomicamente
26
  com hash h(σ)=SHA-256. Na leitura, se h(σ')=h(σ) então σ'=σ (colisão
27
  desprezível, 2^−256), logo retomadas reproduzem EXATAMENTE o estado salvo.
 
85
  meta["n_tensores"] = len(tensores)
86
  destino = self._caminho_tag(tag)
87
  os.makedirs(destino, exist_ok=True)
88
+ # v23 (Teo 35.3): escrita ATÔMICA — tmp + fsync + os.replace. Um crash
89
+ # em qualquer ponto deixa ou o par antigo completo, ou o novo completo.
90
+ cam_tensor = os.path.join(destino, "modelo.safetensors")
91
+ cam_meta = os.path.join(destino, "meta.json")
92
+ tmp_tensor = cam_tensor + ".tmp"
93
+ tmp_meta = cam_meta + ".tmp"
94
+ with open(tmp_tensor, "wb") as f:
95
  f.write(dados)
96
+ f.flush()
97
+ os.fsync(f.fileno())
98
+ with open(tmp_meta, "w", encoding="utf-8") as f:
99
  json.dump(meta, f, ensure_ascii=False, indent=2)
100
+ f.flush()
101
+ os.fsync(f.fileno())
102
+ os.replace(tmp_tensor, cam_tensor)
103
+ os.replace(tmp_meta, cam_meta)
104
+ # fsync do DIRETÓRIO: garante que as entradas renomeadas persistem
105
+ try:
106
+ fd_dir = os.open(destino, os.O_RDONLY)
107
+ try:
108
+ os.fsync(fd_dir)
109
+ finally:
110
+ os.close(fd_dir)
111
+ except OSError:
112
+ pass
113
  # higiene: mantém a última tag + tags protegidas (fases do gate de
114
+ # rollback: pre-dpo/fase-dpo/pre-som/fase-som — Teorema 20.3).
115
+ # v23: a limpeza só acontece DEPOIS do par novo completo+fsync.
116
  removidos = 0
117
  for nome in os.listdir(self.dir_local):
118
  if nome != tag and nome not in self.tags_protegidas:
 
138
  with open(caminho, "rb") as f:
139
  dados = f.read()
140
  meta_path = os.path.join(self._caminho_tag(tag), "meta.json")
141
+ # v23 (Teo 35.3-ii): validação de hash OBRIGATÓRIA. Antes, meta
142
+ # ausente pulava a checagem em silêncio e aceitava tensor truncado.
143
+ if not os.path.exists(meta_path):
144
+ self.eventos.append(
145
+ f"checkpoint {tag}: meta.json ausente — REJEITADO (hash "
146
+ "não verificável; estado possivelmente truncado)")
147
+ return False
148
+ with open(meta_path, encoding="utf-8") as f:
149
+ esperado = json.load(f).get("hash_sha256")
150
+ if not esperado or _sha256_bytes(dados) != esperado:
151
+ self.eventos.append(f"checkpoint {tag}: HASH divergente/ausente — rejeitado")
152
+ return False
153
  try:
154
  sd = _st_load(dados)
155
  # compat v5: snapshots antigos SEM prefixo "modelo/" (sem estado
src/khtst/dados/streaming.py CHANGED
@@ -27,6 +27,7 @@ import io
27
  import json
28
  import os
29
  import shutil
 
30
 
31
  import numpy as np
32
  import pyarrow.parquet as pq
@@ -42,7 +43,46 @@ CACHE_TMP = "/home/z/my-project/cache_hub_tmp"
42
 
43
 
44
  def _hash_texto(t: str) -> str:
45
- return hashlib.sha1(t.encode("utf-8", "ignore")).hexdigest()[:16]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
 
48
  def limpar_cache_temp():
@@ -857,21 +897,29 @@ class StreamingCorpus:
857
  {"id": "marcelohaps/verbovision-oracle-facts-pt", "ad": _ad_verbovision,
858
  "via": "stream", "max": 220},
859
  {"id": "amalia-llm/MMMU-PT", "ad": _ad_mmmu, "via": "stream",
860
- "split": "validation", "max": 110},
 
861
  {"id": "amalia-llm/MMMU-Pro-PT", "ad": _ad_mmmu, "via": "stream",
862
- "split": "test", "max": 90},
 
863
  {"id": "amalia-llm/OCRBench-PT", "ad": _ad_ocrbench, "via": "stream",
864
- "split": "test", "max": 140},
 
865
  {"id": "amalia-llm/COCO-Caption2017-PT", "ad": _ad_coco_pt, "via": "stream",
866
- "split": "validation", "max": 190},
 
867
  {"id": "weikaih/imaginative-perception-token-pt-eval-real", "ad": _ad_weikaih,
868
- "via": "stream", "split": "td_path", "max": 110},
 
869
  {"id": "opedromartins/asr-leaderboard-datasets-ptbr", "ad": _ad_asr,
870
- "via": "stream", "config": "mls", "split": "test", "max": 60},
 
871
  {"id": "opedromartins/asr-leaderboard-datasets-ptbr#tedx", "ad": _ad_asr,
872
- "via": "stream", "config": "tedx", "split": "test", "max": 50},
 
873
  {"id": "opedromartins/asr-leaderboard-datasets-ptbr#fleurs", "ad": _ad_asr,
874
- "via": "stream", "config": "fleurs", "split": "test", "max": 50},
 
875
  {"id": "marcosremar2/pt-br-tts-synth", "ad": None, "via": "tts_marcos",
876
  "max": 240, "n_audio": 14},
877
  {"id": "leeaandrob/neurogrid-tts-synth-ptbr", "ad": None, "via": "degradado",
@@ -893,7 +941,7 @@ class StreamingCorpus:
893
  {"id": "laicsiifes/flickr30k-pt-br-human-translated", "ad": _ad_flickr30k,
894
  "via": "stream", "max": 160},
895
  {"id": "MoreThought/Fable-5.1-Max-Reasoning-Filtered-5000x", "ad": _ad_fable,
896
- "via": "stream", "split": "lite", "max": 160},
897
  {"id": "mvp-lab/LLaVA-OneVision-1.5-Mid-Training-85M", "ad": _ad_llava_mid,
898
  "via": "stream", "max": 160},
899
  # --- degradado documentado (RAM/disco) ---
@@ -916,13 +964,14 @@ class StreamingCorpus:
916
  {"id": "pltops/evosynth-gsm8k-smoke-cerebras-gsm8k",
917
  "ad": _ad_matematica_generica(), "via": "stream", "max": 80},
918
  {"id": "HuggingFaceH4/MATH-500", "ad": _ad_matematica_generica(),
919
- "via": "stream", "split": "test", "max": 100},
 
920
  {"id": "EleutherAI/hendrycks_math#algebra",
921
  "ad": _ad_matematica_generica(), "via": "stream",
922
- "config": "algebra", "max": 50},
923
  {"id": "EleutherAI/hendrycks_math#number_theory",
924
  "ad": _ad_matematica_generica(), "via": "stream",
925
- "config": "number_theory", "max": 40},
926
  {"id": "open-r1/OpenR1-Math-220k", "ad": _ad_matematica_generica(
927
  chave_p=("problem",), chave_s=("solution",), chave_r=("answer",)),
928
  "via": "stream", "max": 100},
@@ -996,11 +1045,25 @@ class StreamingCorpus:
996
 
997
  def coletar(self) -> tuple[list[dict], list[EventoDataset]]:
998
  """Coleta sequencial (um dataset por vez) com dedup + limites +
999
- limpeza de cache temporário após cada fonte."""
 
 
 
 
 
 
 
 
1000
  vistos: set[str] = set()
1001
  registros: list[dict] = []
1002
  for plano in self.PLANO:
1003
  fonte, ad, maximo = plano["id"], plano["ad"], plano["max"]
 
 
 
 
 
 
1004
  n_ok = 0
1005
  for ex in self._iterar_fonte(plano):
1006
  if n_ok >= maximo:
@@ -1008,8 +1071,13 @@ class StreamingCorpus:
1008
  reg = ad(ex) if ad else (ex if via_especial(plano["via"]) else None)
1009
  if reg is None:
1010
  continue
1011
- chave = _hash_texto(reg.get("texto") or reg.get("saida")
1012
- or reg.get("entrada") or "")
 
 
 
 
 
1013
  if chave in vistos:
1014
  continue
1015
  vistos.add(chave)
 
27
  import json
28
  import os
29
  import shutil
30
+ import unicodedata
31
 
32
  import numpy as np
33
  import pyarrow.parquet as pq
 
43
 
44
 
45
  def _hash_texto(t: str) -> str:
46
+ # v23 (P11/T-08): NFC no ponto de entrada — o BPE foi treinado em NFC
47
+ # (0/1.500 textos reais em NFD, medido); texto NFD custa 1,198× tokens
48
+ # e quebra o dedup entre formas canônicas. NFC é idempotente ⟹ zero
49
+ # custo para o texto já canônico (Teo 35.6).
50
+ return hashlib.sha1(unicodedata.normalize("NFC", t)
51
+ .encode("utf-8", "ignore")).hexdigest()[:16]
52
+
53
+
54
+ def _hash_midia(m) -> str:
55
+ """v23 (P4/T-02): hash estável da mídia para a chave de dedup.
56
+ bytes → sha1 dos bytes; str (path/uri) → sha1 do caminho; PIL/numpy →
57
+ sha1 dos bytes crus; None → vazio. NUNCA inventa conteúdo."""
58
+ if m is None:
59
+ return ""
60
+ try:
61
+ if isinstance(m, (bytes, bytearray)):
62
+ return hashlib.sha1(bytes(m)).hexdigest()[:12]
63
+ if isinstance(m, str):
64
+ return hashlib.sha1(m.encode("utf-8", "ignore")).hexdigest()[:12]
65
+ if hasattr(m, "tobytes"):
66
+ return hashlib.sha1(m.tobytes()).hexdigest()[:12]
67
+ except Exception:
68
+ return ""
69
+ return ""
70
+
71
+
72
+ def _chave_dedup(reg: dict) -> str:
73
+ """v23 (Teo 35.7, correção P4/T-02): a chave antiga era
74
+ `texto or saida or entrada` — para registros seq2seq (entrada→saida,
75
+ sem texto) só a SAÍDA entrava: duas perguntas diferentes com a mesma
76
+ resposta colidiam (29,5% do corpus real descartado — medido na
77
+ auditoria; a chave correta recupera +24%). A chave nova cobre os três
78
+ campos de texto + hash da mídia: injetiva na tupla (texto, entrada,
79
+ saida, mídia) com probabilidade 1−2^−64 (sha1-16 hex por campo).
80
+ Idempotente NFC (via _hash_texto/_hash_midia sobre formas canônicas)."""
81
+ return "|".join((_hash_texto(reg.get("texto") or ""),
82
+ _hash_texto(reg.get("entrada") or ""),
83
+ _hash_texto(reg.get("saida") or ""),
84
+ _hash_midia(reg.get("imagem")) or
85
+ _hash_midia(reg.get("audio"))))
86
 
87
 
88
  def limpar_cache_temp():
 
897
  {"id": "marcelohaps/verbovision-oracle-facts-pt", "ad": _ad_verbovision,
898
  "via": "stream", "max": 220},
899
  {"id": "amalia-llm/MMMU-PT", "ad": _ad_mmmu, "via": "stream",
900
+ "split": "validation", "max": 110,
901
+ "papel": "avaliacao"}, # v23 P9: benchmark avaliado — só validation
902
  {"id": "amalia-llm/MMMU-Pro-PT", "ad": _ad_mmmu, "via": "stream",
903
+ "split": "test", "max": 90,
904
+ "papel": "avaliacao"}, # v23 P9
905
  {"id": "amalia-llm/OCRBench-PT", "ad": _ad_ocrbench, "via": "stream",
906
+ "split": "test", "max": 140,
907
+ "papel": "avaliacao"}, # v23 P9
908
  {"id": "amalia-llm/COCO-Caption2017-PT", "ad": _ad_coco_pt, "via": "stream",
909
+ "split": "validation", "max": 190,
910
+ "papel": "avaliacao"}, # v23 P9: probe datasets-server — SÓ validation
911
  {"id": "weikaih/imaginative-perception-token-pt-eval-real", "ad": _ad_weikaih,
912
+ "via": "stream", "split": "td_path", "max": 110,
913
+ "papel": "avaliacao"}, # v23 P9: fonte É um benchmark de avaliação
914
  {"id": "opedromartins/asr-leaderboard-datasets-ptbr", "ad": _ad_asr,
915
+ "via": "stream", "config": "mls", "split": "test", "max": 60,
916
+ "papel": "avaliacao"}, # v23 P9: todos os configs são test-only (probe)
917
  {"id": "opedromartins/asr-leaderboard-datasets-ptbr#tedx", "ad": _ad_asr,
918
+ "via": "stream", "config": "tedx", "split": "test", "max": 50,
919
+ "papel": "avaliacao"}, # v23 P9
920
  {"id": "opedromartins/asr-leaderboard-datasets-ptbr#fleurs", "ad": _ad_asr,
921
+ "via": "stream", "config": "fleurs", "split": "test", "max": 50,
922
+ "papel": "avaliacao"}, # v23 P9
923
  {"id": "marcosremar2/pt-br-tts-synth", "ad": None, "via": "tts_marcos",
924
  "max": 240, "n_audio": 14},
925
  {"id": "leeaandrob/neurogrid-tts-synth-ptbr", "ad": None, "via": "degradado",
 
941
  {"id": "laicsiifes/flickr30k-pt-br-human-translated", "ad": _ad_flickr30k,
942
  "via": "stream", "max": 160},
943
  {"id": "MoreThought/Fable-5.1-Max-Reasoning-Filtered-5000x", "ad": _ad_fable,
944
+ "via": "stream", "split": "train", "max": 160},
945
  {"id": "mvp-lab/LLaVA-OneVision-1.5-Mid-Training-85M", "ad": _ad_llava_mid,
946
  "via": "stream", "max": 160},
947
  # --- degradado documentado (RAM/disco) ---
 
964
  {"id": "pltops/evosynth-gsm8k-smoke-cerebras-gsm8k",
965
  "ad": _ad_matematica_generica(), "via": "stream", "max": 80},
966
  {"id": "HuggingFaceH4/MATH-500", "ad": _ad_matematica_generica(),
967
+ "via": "stream", "split": "test", "max": 100,
968
+ "papel": "avaliacao"}, # v23 P9: MATH-500 É um conjunto de avaliação
969
  {"id": "EleutherAI/hendrycks_math#algebra",
970
  "ad": _ad_matematica_generica(), "via": "stream",
971
+ "config": "algebra", "split": "train", "max": 50},
972
  {"id": "EleutherAI/hendrycks_math#number_theory",
973
  "ad": _ad_matematica_generica(), "via": "stream",
974
+ "config": "number_theory", "split": "train", "max": 40},
975
  {"id": "open-r1/OpenR1-Math-220k", "ad": _ad_matematica_generica(
976
  chave_p=("problem",), chave_s=("solution",), chave_r=("answer",)),
977
  "via": "stream", "max": 100},
 
1045
 
1046
  def coletar(self) -> tuple[list[dict], list[EventoDataset]]:
1047
  """Coleta sequencial (um dataset por vez) com dedup + limites +
1048
+ limpeza de cache temporário após cada fonte.
1049
+
1050
+ v23 (P9/T-03, Teo 35.14): fontes com `papel == "avaliacao"` NÃO
1051
+ entram no corpus de treino (vazamento eval→treino — 9 fontes de
1052
+ benchmark: MMMU/MMMU-Pro/OCRBench/COCO-Caption [só validation/test,
1053
+ verificado no datasets-server], weikaih-eval-real, ASR×3 [test-only],
1054
+ MATH-500). A válvula KHTST_INCLUIR_AVALIACAO=1 reabre EXPLICITAMENTE
1055
+ (para coletas de benchmark; o default é a honestidade do treino)."""
1056
+ incluir_avaliacao = os.environ.get("KHTST_INCLUIR_AVALIACAO", "") == "1"
1057
  vistos: set[str] = set()
1058
  registros: list[dict] = []
1059
  for plano in self.PLANO:
1060
  fonte, ad, maximo = plano["id"], plano["ad"], plano["max"]
1061
+ if plano.get("papel") == "avaliacao" and not incluir_avaliacao:
1062
+ self.eventos.append(EventoDataset(
1063
+ fonte, "PULADA",
1064
+ "split de avaliação (vazamento eval→treino bloqueado — "
1065
+ "P9/T-03; KHTST_INCLUIR_AVALIACAO=1 reabre)"))
1066
+ continue
1067
  n_ok = 0
1068
  for ex in self._iterar_fonte(plano):
1069
  if n_ok >= maximo:
 
1071
  reg = ad(ex) if ad else (ex if via_especial(plano["via"]) else None)
1072
  if reg is None:
1073
  continue
1074
+ # v23 (P11): NFC nos campos de texto ANTES do dedup e do
1075
+ # armazenamento — canonicalidade única no corpus inteiro.
1076
+ for _campo in ("texto", "entrada", "saida"):
1077
+ v = reg.get(_campo)
1078
+ if isinstance(v, str) and v:
1079
+ reg[_campo] = unicodedata.normalize("NFC", v)
1080
+ chave = _chave_dedup(reg)
1081
  if chave in vistos:
1082
  continue
1083
  vistos.add(chave)
src/khtst/ipc/_fallback.py CHANGED
@@ -96,6 +96,19 @@ class KHTSTCanal:
96
  magic = c._ler_u64(_OFF_MAGIC) & 0xFFFFFFFF
97
  if magic != MAGIC_KHTST:
98
  raise ValueError(f"magic inválido na região: 0x{magic:08X}")
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  return c
100
 
101
  def _ler_u64(self, off: int) -> int:
 
96
  magic = c._ler_u64(_OFF_MAGIC) & 0xFFFFFFFF
97
  if magic != MAGIC_KHTST:
98
  raise ValueError(f"magic inválido na região: 0x{magic:08X}")
99
+ # v23 (Teo 35.4 — correção I-01/P6 do plano de auditoria): a
100
+ # capacidade gravada no header pelo PRODUTOR é lida e EXIGIDA
101
+ # consistente com a capacidade derivada do size recebido. Antes,
102
+ # um consumidor com size divergente anexava em silêncio e lia
103
+ # posições erradas quando tail ≥ cap própria (corrupção CRC32
104
+ # medida) ou 0 frames — agora a divergência é um erro ALTO e
105
+ # imediato, diagnóstico único na fronteira do processo.
106
+ cap_header = int(c._ler_u64(_OFF_CAPACITY))
107
+ if cap_header > 0 and cap_header != c._cap:
108
+ raise ValueError(
109
+ f"size divergente do produtor: capacidade do header "
110
+ f"({cap_header}) ≠ capacidade derivada de size={size} "
111
+ f"({c._cap}) — reanexe com o size do produtor")
112
  return c
113
 
114
  def _ler_u64(self, off: int) -> int:
src/khtst/ipc/khtst_ipc.c CHANGED
The diff for this file is too large to render. See raw diff
 
src/khtst/ipc/khtst_ipc.cpython-312-x86_64-linux-gnu.so CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:0460ea341b8ee6d872ff0bfa7281e25e660d02a0264848486e904f61b01dc968
3
- size 1771456
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1133441beccf124ee093e0b46936992b5b59d295501c3cf7006642ddc24e93ca
3
+ size 1779384
src/khtst/ipc/khtst_ipc.pyx CHANGED
@@ -270,7 +270,10 @@ cdef class KHTSTCanal:
270
 
271
  @classmethod
272
  def anexar(cls, int fd, size_t size):
273
- """Anexa a uma região JÁ inicializada (FD recebido via SCM_RIGHTS)."""
 
 
 
274
  cdef KHTSTCanal c = cls.__new__(cls)
275
  c._mapear(fd, size)
276
  cdef uint32_t magic = 0
@@ -280,6 +283,13 @@ cdef class KHTSTCanal:
280
  c._cap = _next_pow2(size - _HEADER_SIZE) // 2
281
  if c._cap == 0 or (c._cap & (c._cap - 1)) != 0:
282
  raise ValueError(f"capacidade não é potência de 2: {c._cap}")
 
 
 
 
 
 
 
283
  c._fd = fd
284
  c._eh_dono = 0
285
  return c
 
270
 
271
  @classmethod
272
  def anexar(cls, int fd, size_t size):
273
+ """Anexa a uma região JÁ inicializada (FD recebido via SCM_RIGHTS).
274
+ v23 (Teo 35.4): valida a capacidade do HEADER contra a derivada do
275
+ size — divergência do produtor é erro ALTO (antes: leitura em
276
+ posições erradas quando tail ≥ cap própria, corrupção silenciosa)."""
277
  cdef KHTSTCanal c = cls.__new__(cls)
278
  c._mapear(fd, size)
279
  cdef uint32_t magic = 0
 
283
  c._cap = _next_pow2(size - _HEADER_SIZE) // 2
284
  if c._cap == 0 or (c._cap & (c._cap - 1)) != 0:
285
  raise ValueError(f"capacidade não é potência de 2: {c._cap}")
286
+ cdef unsigned long long cap_header = 0
287
+ memcpy(&cap_header, c._base + _OFF_CAPACITY, 8)
288
+ if cap_header > 0 and (<size_t> cap_header) != c._cap:
289
+ raise ValueError(
290
+ f"size divergente do produtor: capacidade do header "
291
+ f"({cap_header}) ≠ capacidade derivada de size={size} "
292
+ f"({c._cap}) — reanexe com o size do produtor")
293
  c._fd = fd
294
  c._eh_dono = 0
295
  return c
src/khtst/mamba3/tronco.py CHANGED
@@ -84,10 +84,15 @@ class CamadaTroncoM3(nn.Module):
84
 
85
  # ----------------------------------------------------------- forward
86
  def forward(self, x, cache_k=None, cache_v=None, kv_max=512, coletar=None,
87
- tarefa=None):
88
  """x: (B, T, d). Devolve (x, k, v) — k/v da NoPE (None nas Mamba3),
89
  interface v2 preservada. Decode incremental: T==1 + `_geracao` ativa
90
- usa o estado Mamba-3 sedado pelo prefill (Teo 24.13)."""
 
 
 
 
 
91
  self._tarefa_corrente = tarefa
92
  # --- 1) subcamada sequencial ---
93
  h = self.norm1(x)
 
84
 
85
  # ----------------------------------------------------------- forward
86
  def forward(self, x, cache_k=None, cache_v=None, kv_max=512, coletar=None,
87
+ tarefa=None, desloc_rope: int = 0):
88
  """x: (B, T, d). Devolve (x, k, v) — k/v da NoPE (None nas Mamba3),
89
  interface v2 preservada. Decode incremental: T==1 + `_geracao` ativa
90
+ usa o estado Mamba-3 sedado pelo prefill (Teo 24.13).
91
+ v23 (Teo 35.8): `desloc_rope` aceito por UNIFORMIDADE de interface
92
+ com os blocos MoA — aqui é IGNORADO por construção: a subcamada
93
+ sequencial é Mamba-3 (posicionalidade pelo ESTADO, não por índice)
94
+ e a atenção periódica é NoPE (sem encoding posicional — a invariância
95
+ translacional é o próprio desenho, doc 22 §3)."""
96
  self._tarefa_corrente = tarefa
97
  # --- 1) subcamada sequencial ---
98
  h = self.norm1(x)
src/khtst/nucleo/modelo.py CHANGED
@@ -673,13 +673,21 @@ class KHTSTModel(nn.Module):
673
  contador.adiciona(t0)
674
  buffer_oculto: list[torch.Tensor] = []
675
  metas_calibracao: list[dict] = []
 
 
 
 
 
 
676
  for passo in range(max_novos):
677
  co = {}
678
  for li, bloco in enumerate(self.blocos):
679
  ck, cv = caches[li] if caches[li] != (None, None) else (None, None)
680
- x, k, v = bloco(x, cache_k=ck, cache_v=cv, kv_max=kv_max, coletar=co,
681
- tarefa=tarefa)
 
682
  caches[li] = (k, v)
 
683
  x = self.norm_f(x)
684
  # v4 — NLG modula o estado oculto (doc 15 §1: estilo aditivo)
685
  h_dec = self.nlg.condicionar(x) if self.nlg is not None else x
 
673
  contador.adiciona(t0)
674
  buffer_oculto: list[torch.Tensor] = []
675
  metas_calibracao: list[dict] = []
676
+ # v23 (Teo 35.8) — referencial ABSOLUTO de posições do RoPE: a 1ª
677
+ # passada (prefill, T = prompt) usa desloc 0; cada passo de decode
678
+ # (T=1) usa a posição absoluta do token novo. Sem isso, o token novo
679
+ # recebia sempre pos=0 e o offset relativo (t−j) degenerava para
680
+ # (0−j) — score constante em t (bug A-03 da auditoria).
681
+ pos_abs = 0
682
  for passo in range(max_novos):
683
  co = {}
684
  for li, bloco in enumerate(self.blocos):
685
  ck, cv = caches[li] if caches[li] != (None, None) else (None, None)
686
+ x, k, v = bloco(x, cache_k=ck, cache_v=cv, kv_max=kv_max,
687
+ coletar=co, tarefa=tarefa,
688
+ desloc_rope=pos_abs)
689
  caches[li] = (k, v)
690
+ pos_abs += x.shape[1]
691
  x = self.norm_f(x)
692
  # v4 — NLG modula o estado oculto (doc 15 §1: estilo aditivo)
693
  h_dec = self.nlg.condicionar(x) if self.nlg is not None else x
src/khtst/nucleo/validacao.py CHANGED
@@ -9,7 +9,11 @@ o treino aborta com mensagem clara ANTES do primeiro passo.
9
 
10
  Teorema 30.7 (pré-condições): cada verificação abaixo é uma pré-condição
11
  de um teorema já provado no projeto — violá-la invalida a prova a montante:
12
- • mamba3.n_camadas % 6 == 0 ⟹ proporção 5:1 termina em atenção (doc 22 §3);
 
 
 
 
13
  • τ_min > 0 e τ₀ > τ_min ⟹ barreira GSQ (Teo 26.5);
14
  • λ_* ≥ 0 ⟹ pesos vivos e sinal correto (Teos 28.7/29.x);
15
  • lr_min ≤ lr_max, clip > 0 ⟹ Robbins–Monro com passo limitado (Teo 28.5);
@@ -59,6 +63,16 @@ def validar_config(cfg, modelo=None) -> list[str]:
59
  if comp < 2:
60
  problemas.append(f"modelo.comprimento_ctx={comp} deve ser ≥ 2")
61
  m3 = _get(m, "mamba3")
 
 
 
 
 
 
 
 
 
 
62
  if m3 is not None and bool(_get(m3, "ativo", True)):
63
  n_cam = int(_num(_get(m3, "n_camadas", 6)))
64
  if n_cam % 6 != 0:
 
9
 
10
  Teorema 30.7 (pré-condições): cada verificação abaixo é uma pré-condição
11
  de um teorema já provado no projeto — violá-la invalida a prova a montante:
12
+ • modelo.n_camadas % 6 == 0 ⟹ proporção 5:1 termina em atenção (doc 22 §3);
13
+ v23 (Teo 35.5): o check é sobre o campo que CONSTRÓI o tronco
14
+ (modelo.n_camadas → TroncoMamba3) — antes checava modelo.mamba3.n_camadas,
15
+ que é config morta para o tronco (só n_camadas_vl é consumido, pelo
16
+ Mamba3VL), e o tronco entregue (n_camadas=3) tinha ZERO camadas NoPE;
17
  • τ_min > 0 e τ₀ > τ_min ⟹ barreira GSQ (Teo 26.5);
18
  • λ_* ≥ 0 ⟹ pesos vivos e sinal correto (Teos 28.7/29.x);
19
  • lr_min ≤ lr_max, clip > 0 ⟹ Robbins–Monro com passo limitado (Teo 28.5);
 
63
  if comp < 2:
64
  problemas.append(f"modelo.comprimento_ctx={comp} deve ser ≥ 2")
65
  m3 = _get(m, "mamba3")
66
+ # v23 (Teo 35.5, correção A-04): o tronco é construído por
67
+ # TroncoMamba3(d, n_camadas=m.n_camadas) — a validação 5:1 deve
68
+ # checar ESTE campo. Com n_camadas=3 o padrão (i+1)%6==0 produz
69
+ # ZERO camadas NoPE e o contrato híbrido 5:1 não existe.
70
+ n_cam_tronco = int(_num(_get(m, "n_camadas", 1)))
71
+ if n_cam_tronco % 6 != 0:
72
+ problemas.append(
73
+ f"modelo.n_camadas={n_cam_tronco} deve ser MÚLTIPLO DE 6 "
74
+ "(o tronco híbrido 5:1 Mamba:NoPE é construído por ESTE campo "
75
+ "— Teo 22 §3; com n_camadas=3 o padrão (i+1)%6==0 dá 0 NoPE)")
76
  if m3 is not None and bool(_get(m3, "ativo", True)):
77
  n_cam = int(_num(_get(m3, "n_camadas", 6)))
78
  if n_cam % 6 != 0:
src/khtst/percepcao/atencao.py CHANGED
@@ -36,11 +36,24 @@ RAIO_JANELA_PADRAO = 128 # era 32 (×4)
36
  KV_MAX_PADRAO = 2048 # era 512 (×4)
37
 
38
 
39
- def aplicar_rope(q: torch.Tensor, k: torch.Tensor, base: float = 10000.0) -> tuple[torch.Tensor, torch.Tensor]:
 
40
  """RoPE padrão. q,k: (B, H, T, dk). Rotação relativa preserva produto interno
41
- dependente apenas da diferença de posições (prova: e^{iθp} conjugação)."""
 
 
 
 
 
 
 
 
 
 
 
 
42
  B, H, T, dk = q.shape
43
- pos = torch.arange(T, device=q.device, dtype=torch.float32)
44
  freqs = base ** (-torch.arange(0, dk, 2, device=q.device, dtype=torch.float32) / dk)
45
  ang = torch.outer(pos, freqs) # (T, dk/2)
46
  cos, sin = ang.cos(), ang.sin()
@@ -92,12 +105,14 @@ class AtencaoCabecas(nn.Module):
92
  self.dropout = dropout
93
 
94
  def forward(self, x: torch.Tensor, cache_k: torch.Tensor | None = None,
95
- cache_v: torch.Tensor | None = None, kv_max: int = KV_MAX_PADRAO):
 
96
  B, T, D = x.shape
97
  q = self.wq(x).view(B, T, self.h, self.dk).transpose(1, 2)
98
  k = self.wk(x).view(B, T, self.h, self.dk).transpose(1, 2)
99
  v = self.wv(x).view(B, T, self.h, self.dk).transpose(1, 2)
100
- q, k = aplicar_rope(q, k)
 
101
  if cache_k is not None: # decodificação: 1 token novo
102
  k = torch.cat([cache_k, k], dim=2)
103
  v = torch.cat([cache_v, v], dim=2)
@@ -171,12 +186,13 @@ class AtencaoArvore(nn.Module):
171
 
172
  def forward(self, x: torch.Tensor, cache_k: torch.Tensor | None = None,
173
  cache_v: torch.Tensor | None = None, kv_max: int = KV_MAX_PADRAO,
174
- causal: bool = True):
175
  B, T, D = x.shape
176
  q = self.wq(x).view(B, T, self.h, self.dk).transpose(1, 2)
177
  k = self.wk(x).view(B, T, self.h, self.dk).transpose(1, 2)
178
  v = self.wv(x).view(B, T, self.h, self.dk).transpose(1, 2)
179
- q, k = aplicar_rope(q, k)
 
180
  if cache_k is not None:
181
  k = torch.cat([cache_k, k], dim=2)
182
  v = torch.cat([cache_v, v], dim=2)
@@ -312,26 +328,54 @@ class MixtureOfAttention(nn.Module):
312
  if usar_arvore else None
313
 
314
  def forward(self, x, cache_k=None, cache_v=None, kv_max=KV_MAX_PADRAO,
315
- coletar=None, estado_linear=None):
316
  if self.gate_gsq is not None: # v14 — gate GSQ (3 opções)
317
  g = self.gate_gsq()
318
  if coletar is not None and hasattr(self.gate_gsq, "metrica"):
319
  coletar["gate_gsq"] = self.gate_gsq.metrica()
320
  else:
321
  g = F.softmax(self.logits_gate, dim=0)
322
- y_g, k, v = self.global_(x, cache_k, cache_v, kv_max)
323
- y_j, _, _ = self.janela(x, cache_k, cache_v, kv_max)
 
 
 
 
324
  y_l, est = self.linear(x, estado=estado_linear)
325
  y = g[0] * y_g + g[1] * y_j + g[2] * y_l
326
- _peso_arv = float(self.peso_arvore.detach()) if self.peso_arvore is not None else 0.0
327
- if self.arvore is not None and _peso_arv != 0.0:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  # fora de ordem + bidirecional no espaço árvore; no decoder causal
329
  # a árvore preserva prefixo (ordem Euler) — Teorema 19.3/19.5
330
  y_a, _, _ = self.arvore(x, cache_k, cache_v, kv_max,
331
- causal=(cache_k is None))
332
- y = y + _peso_arv * y_a
 
 
 
 
 
 
 
 
 
333
  if coletar is not None:
334
- coletar["peso_arvore"] = _peso_arv
335
  if coletar is not None:
336
  coletar["gate_atencao"] = [float(v_) for v_ in g.detach()]
337
  return y, k, v, est
 
36
  KV_MAX_PADRAO = 2048 # era 512 (×4)
37
 
38
 
39
+ def aplicar_rope(q: torch.Tensor, k: torch.Tensor, base: float = 10000.0,
40
+ desloc: int = 0) -> tuple[torch.Tensor, torch.Tensor]:
41
  """RoPE padrão. q,k: (B, H, T, dk). Rotação relativa preserva produto interno
42
+ dependente apenas da diferença de posições (prova: e^{iθp} conjugação).
43
+
44
+ v23 (Teo 35.8 — correção A-03/P2 da auditoria): `desloc` é a posição
45
+ ABSOLUTA do primeiro elemento de q/k nesta chamada. A propriedade
46
+ ⟨R_p q, R_j k⟩ = ⟨q, R_{j−p} k⟩ exige que q e k compartilhem a MESMA
47
+ origem de posições: no prefill ambos usam desloc=0 (idêntico ao v22,
48
+ bit a bit); na decodificação com KV-cache o token novo ocupa a posição
49
+ absoluta `desloc` = nº de tokens já no cache — sem isso o offset
50
+ relativo (t−j) degenerava para (0−j) e o score ficava CONSTANTE em t
51
+ (medido na auditoria: 2,756113 para t=1,2,3,10). O recorte kv_max é
52
+ inofensivo: a rotação é aplicada ANTES do cache — as chaves antigas
53
+ carregam suas rotações absolutas originais.
54
+ """
55
  B, H, T, dk = q.shape
56
+ pos = torch.arange(desloc, desloc + T, device=q.device, dtype=torch.float32)
57
  freqs = base ** (-torch.arange(0, dk, 2, device=q.device, dtype=torch.float32) / dk)
58
  ang = torch.outer(pos, freqs) # (T, dk/2)
59
  cos, sin = ang.cos(), ang.sin()
 
105
  self.dropout = dropout
106
 
107
  def forward(self, x: torch.Tensor, cache_k: torch.Tensor | None = None,
108
+ cache_v: torch.Tensor | None = None, kv_max: int = KV_MAX_PADRAO,
109
+ desloc_rope: int = 0):
110
  B, T, D = x.shape
111
  q = self.wq(x).view(B, T, self.h, self.dk).transpose(1, 2)
112
  k = self.wk(x).view(B, T, self.h, self.dk).transpose(1, 2)
113
  v = self.wv(x).view(B, T, self.h, self.dk).transpose(1, 2)
114
+ # v23 (Teo 35.8): desloc_rope = posição absoluta do 1º token de x
115
+ q, k = aplicar_rope(q, k, desloc=desloc_rope)
116
  if cache_k is not None: # decodificação: 1 token novo
117
  k = torch.cat([cache_k, k], dim=2)
118
  v = torch.cat([cache_v, v], dim=2)
 
186
 
187
  def forward(self, x: torch.Tensor, cache_k: torch.Tensor | None = None,
188
  cache_v: torch.Tensor | None = None, kv_max: int = KV_MAX_PADRAO,
189
+ causal: bool = True, desloc_rope: int = 0):
190
  B, T, D = x.shape
191
  q = self.wq(x).view(B, T, self.h, self.dk).transpose(1, 2)
192
  k = self.wk(x).view(B, T, self.h, self.dk).transpose(1, 2)
193
  v = self.wv(x).view(B, T, self.h, self.dk).transpose(1, 2)
194
+ # v23 (Teo 35.8): desloc_rope = posição absoluta do 1º token de x
195
+ q, k = aplicar_rope(q, k, desloc=desloc_rope)
196
  if cache_k is not None:
197
  k = torch.cat([cache_k, k], dim=2)
198
  v = torch.cat([cache_v, v], dim=2)
 
328
  if usar_arvore else None
329
 
330
  def forward(self, x, cache_k=None, cache_v=None, kv_max=KV_MAX_PADRAO,
331
+ coletar=None, estado_linear=None, desloc_rope: int = 0):
332
  if self.gate_gsq is not None: # v14 — gate GSQ (3 opções)
333
  g = self.gate_gsq()
334
  if coletar is not None and hasattr(self.gate_gsq, "metrica"):
335
  coletar["gate_gsq"] = self.gate_gsq.metrica()
336
  else:
337
  g = F.softmax(self.logits_gate, dim=0)
338
+ # v23 (Teo 35.8): desloc_rope propagado aos mecanismos com RoPE —
339
+ # global/janela/árvore compartilham o mesmo referencial de posições
340
+ y_g, k, v = self.global_(x, cache_k, cache_v, kv_max,
341
+ desloc_rope=desloc_rope)
342
+ y_j, _, _ = self.janela(x, cache_k, cache_v, kv_max,
343
+ desloc_rope=desloc_rope)
344
  y_l, est = self.linear(x, estado=estado_linear)
345
  y = g[0] * y_g + g[1] * y_j + g[2] * y_l
346
+ # v23 (Teo 35.13, correção A-23 da auditoria): o curto-circuito
347
+ # `_peso_arv != 0.0` tornava o mecanismo ÁRVORE PERMANENTEMENTE
348
+ # INERTE — `peso_arvore` nasce em 0 (nascimento neutro, Teo 19.4),
349
+ # nunca é escrito quando y_a não é computado, e ∂y/∂peso_arvore =
350
+ # y_a nunca existia ⟹ o parâmetro nunca saía de 0 (mecanismo
351
+ # morto desde a v8). Correção com NASCIMENTO NEUTRO PRESERVADO:
352
+ # em TREINO com gradiente ativo, y_a é SEMPRE computado e o produto
353
+ # é pelo TENSOR — y + 0·y_a = y bit a bit (IEEE-754: 0·finito = ±0
354
+ # e x ± 0 = x exato), o gradiente ∂y/∂peso = y_a flui desde o 1º
355
+ # passo e o forward no nascimento é idêntico ao v≤22. Em eval /
356
+ # decode / no_grad o curto-circuito permanece (custo zero quando
357
+ # o peso ainda é 0 — orçamento de RAM/CPU do projeto).
358
+ _treino_arv = (self.arvore is not None
359
+ and self.peso_arvore is not None
360
+ and self.training and torch.is_grad_enabled()
361
+ and self.peso_arvore.requires_grad)
362
+ if _treino_arv:
363
  # fora de ordem + bidirecional no espaço árvore; no decoder causal
364
  # a árvore preserva prefixo (ordem Euler) — Teorema 19.3/19.5
365
  y_a, _, _ = self.arvore(x, cache_k, cache_v, kv_max,
366
+ causal=(cache_k is None),
367
+ desloc_rope=desloc_rope)
368
+ y = y + self.peso_arvore * y_a
369
+ if coletar is not None:
370
+ coletar["peso_arvore"] = float(self.peso_arvore.detach())
371
+ elif self.arvore is not None and self.peso_arvore is not None \
372
+ and float(self.peso_arvore.detach()) != 0.0:
373
+ y_a, _, _ = self.arvore(x, cache_k, cache_v, kv_max,
374
+ causal=(cache_k is None),
375
+ desloc_rope=desloc_rope)
376
+ y = y + float(self.peso_arvore.detach()) * y_a
377
  if coletar is not None:
378
+ coletar["peso_arvore"] = float(self.peso_arvore.detach())
379
  if coletar is not None:
380
  coletar["gate_atencao"] = [float(v_) for v_ in g.detach()]
381
  return y, k, v, est
src/khtst/percepcao/blocos.py CHANGED
@@ -56,13 +56,14 @@ class Bloco(nn.Module):
56
  self._tarefa_corrente: str | None = None
57
 
58
  def forward(self, x, cache_k=None, cache_v=None, kv_max=512, coletar=None,
59
- tarefa=None):
60
  # Caminho recorrente (estado linear) SÓ na decodificação pura (T==1);
61
  # treino e avaliação com T>1 usam o caminho paralelo exato.
62
  est_in = getattr(self, "_estado_linear", None) \
63
  if (not self.training and x.shape[1] == 1) else None
 
64
  h, k, v, est_out = self.atencao(self.norm1(x), cache_k, cache_v, kv_max, coletar,
65
- estado_linear=est_in)
66
  # estado da atenção linear persiste no prefill e na decodificação
67
  self._estado_linear = est_out if est_out is not None else None
68
  x = x + h
@@ -102,11 +103,12 @@ class BlocoV3(nn.Module):
102
  self.composto.registrar_confianca(h_t)
103
 
104
  def forward(self, x, cache_k=None, cache_v=None, kv_max=512, coletar=None,
105
- tarefa=None):
106
  est_in = getattr(self, "_estado_linear", None) \
107
  if (not self.training and x.shape[1] == 1) else None
 
108
  h, k, v, est_out = self.atencao(self.norm1(x), cache_k, cache_v, kv_max, coletar,
109
- estado_linear=est_in)
110
  self._estado_linear = est_out if est_out is not None else None
111
  x = x + h
112
  self._tarefa_corrente = tarefa
 
56
  self._tarefa_corrente: str | None = None
57
 
58
  def forward(self, x, cache_k=None, cache_v=None, kv_max=512, coletar=None,
59
+ tarefa=None, desloc_rope: int = 0):
60
  # Caminho recorrente (estado linear) SÓ na decodificação pura (T==1);
61
  # treino e avaliação com T>1 usam o caminho paralelo exato.
62
  est_in = getattr(self, "_estado_linear", None) \
63
  if (not self.training and x.shape[1] == 1) else None
64
+ # v23 (Teo 35.8): desloc_rope propagado à MoA (RoPE com offset)
65
  h, k, v, est_out = self.atencao(self.norm1(x), cache_k, cache_v, kv_max, coletar,
66
+ estado_linear=est_in, desloc_rope=desloc_rope)
67
  # estado da atenção linear persiste no prefill e na decodificação
68
  self._estado_linear = est_out if est_out is not None else None
69
  x = x + h
 
103
  self.composto.registrar_confianca(h_t)
104
 
105
  def forward(self, x, cache_k=None, cache_v=None, kv_max=512, coletar=None,
106
+ tarefa=None, desloc_rope: int = 0):
107
  est_in = getattr(self, "_estado_linear", None) \
108
  if (not self.training and x.shape[1] == 1) else None
109
+ # v23 (Teo 35.8): desloc_rope propagado à MoA (RoPE com offset)
110
  h, k, v, est_out = self.atencao(self.norm1(x), cache_k, cache_v, kv_max, coletar,
111
+ estado_linear=est_in, desloc_rope=desloc_rope)
112
  self._estado_linear = est_out if est_out is not None else None
113
  x = x + h
114
  self._tarefa_corrente = tarefa
src/khtst/percepcao/vit_lra.py CHANGED
@@ -196,9 +196,16 @@ class DetectadorCinza(nn.Module):
196
  (i) IDENTIDADE: imagem com R=G=B (cinza exato) ⟹ χ = 0 ⟹ s = 1;
197
  imagem com croma médio χ̄ ⟹ s = 1 − χ̄ (forma fechada, sem
198
  hiperparâmetro aprendido — detecção PURA, sempre ativa);
199
- (ii) LIPSCHITZ: χ é Lipschitz em I com constante 2/(max_c+ε) —
200
- perturbação δ da imagem muda s por ≤ 2δ (robustez — a detecção
201
- NÃO oscila com ruído de sensor);
 
 
 
 
 
 
 
202
  (iii) USO EM PARALELO: a ramificação 1-bit (contorno) é INTRINSECAMENTE
203
  cinza (usa só luminância) — s pondera QUANTA confiança dar à
204
  ramificação 4-bit colorida: em imagens quase-cinza (s→1) a
 
196
  (i) IDENTIDADE: imagem com R=G=B (cinza exato) ⟹ χ = 0 ⟹ s = 1;
197
  imagem com croma médio χ̄ ⟹ s = 1 − χ̄ (forma fechada, sem
198
  hiperparâmetro aprendido — detecção PURA, sempre ativa);
199
+ (ii) LIPSCHITZ (doc 27, Teo 27.7(ii-a) — CORRIGIDO v23): χ é
200
+ Lipschitz com |∂χ/∂I| ≤ (mx + mn + 2ε)/(mx+ε)² ≤ 2/(mx+ε) + χ/(mx+ε)
201
+ — uma perturbação δ da imagem muda s por NO MÁXIMO 2δ/(mx+ε)
202
+ (não "2δ": o bound ingênuo do docstring antigo era FALSO —
203
+ pixel cinza 0,01 com um canal +0,001 dá |Δs| = 0,0901 = 45×
204
+ 2δ, medido; o TEOREMA do doc 27 sempre esteve correto,
205
+ razão real/bound = 0,999989). Ressalva honesta: para imagens
206
+ bem-expostas (mx ≥ m̄) vale |Δs| ≲ 2δ/m̄ — a robustez DEGRADA
207
+ em pixels quase-pretos, exatamente onde o gate γ_c é mais
208
+ conservador;
209
  (iii) USO EM PARALELO: a ramificação 1-bit (contorno) é INTRINSECAMENTE
210
  cinza (usa só luminância) — s pondera QUANTA confiança dar à
211
  ramificação 4-bit colorida: em imagens quase-cinza (s→1) a
src/khtst/servico/adhoc.py CHANGED
@@ -261,6 +261,8 @@ class SessaoKHTST:
261
  self.modelo.blocos.ativar_modo_geracao(1, x.device, x.dtype)
262
  contextos = [int(t) for t in p.ids_prompt]
263
  V = self.modelo.cfg.modelo.vocab
 
 
264
  novo_token: bool = True
265
  while novo_token and passos < max_passos:
266
  # ---- BOMBA ADHOC: processa I/O, stop e continue (Teo 19.14) ----
@@ -280,8 +282,10 @@ class SessaoKHTST:
280
  for li, bloco in enumerate(self.modelo.blocos):
281
  ck, cv = caches[li]
282
  x, k, v = bloco(x, cache_k=ck, cache_v=cv, kv_max=kv_max,
283
- coletar=co, tarefa=p.tarefa)
 
284
  caches[li] = (k, v)
 
285
  x = self.modelo.norm_f(x)
286
  logits = self.modelo.lm_head(x[:, -1]) / max(p.temperatura, 1e-3)
287
  # punição dinâmica de repetição (eq. 10.8/10.9 — doc 10 §8)
 
261
  self.modelo.blocos.ativar_modo_geracao(1, x.device, x.dtype)
262
  contextos = [int(t) for t in p.ids_prompt]
263
  V = self.modelo.cfg.modelo.vocab
264
+ # v23 (Teo 35.8): referencial absoluto do RoPE (prefill + decode)
265
+ pos_abs = 0
266
  novo_token: bool = True
267
  while novo_token and passos < max_passos:
268
  # ---- BOMBA ADHOC: processa I/O, stop e continue (Teo 19.14) ----
 
282
  for li, bloco in enumerate(self.modelo.blocos):
283
  ck, cv = caches[li]
284
  x, k, v = bloco(x, cache_k=ck, cache_v=cv, kv_max=kv_max,
285
+ coletar=co, tarefa=p.tarefa,
286
+ desloc_rope=pos_abs)
287
  caches[li] = (k, v)
288
+ pos_abs += x.shape[1]
289
  x = self.modelo.norm_f(x)
290
  logits = self.modelo.lm_head(x[:, -1]) / max(p.temperatura, 1e-3)
291
  # punição dinâmica de repetição (eq. 10.8/10.9 — doc 10 §8)
src/khtst/telemetria/hub.py CHANGED
@@ -68,6 +68,9 @@ class TelemetryHub:
68
  self._anterior_theta: dict[str, torch.Tensor] | None = None
69
  self.eventos_guard: list[str] = []
70
  self._inicio = time.time()
 
 
 
71
  # v18 (requisito §3.4): logging com NÍVEIS + ROTAÇÃO. Padrão: ligado
72
  # (INFO) ao lado do JSONL; `com_logger=False` desliga (testes).
73
  if logger is not None:
@@ -161,6 +164,33 @@ class TelemetryHub:
161
  "historico_max": self.historico_max,
162
  "log": self.logger.estatisticas() if self.logger else None}
163
 
 
 
 
 
 
 
 
 
 
 
 
164
  def _gravar(self, linha: dict) -> None:
 
 
 
 
 
 
 
 
 
 
165
  with open(self.arquivo, "a", encoding="utf-8") as f:
166
- f.write(json.dumps(linha, ensure_ascii=False) + "\n")
 
 
 
 
 
 
 
68
  self._anterior_theta: dict[str, torch.Tensor] | None = None
69
  self.eventos_guard: list[str] = []
70
  self._inicio = time.time()
71
+ # v23 (Teo 35.10): buffer de escrita do JSONL (lote + borda de tempo)
72
+ self._buffer: list[str] = []
73
+ self._ultimo_flush = self._inicio
74
  # v18 (requisito §3.4): logging com NÍVEIS + ROTAÇÃO. Padrão: ligado
75
  # (INFO) ao lado do JSONL; `com_logger=False` desliga (testes).
76
  if logger is not None:
 
164
  "historico_max": self.historico_max,
165
  "log": self.logger.estatisticas() if self.logger else None}
166
 
167
+ # v23 (Teo 35.10, correção A-19): escrita com BUFFER — a versão antiga
168
+ # abria/fechava o arquivo a cada linha (até ~20 open/close por passo
169
+ # quando atributo+alerta+evento se somam — I/O-bound medido na
170
+ # auditoria). As linhas acumulam em memória e são descarregadas em
171
+ # LOTE quando (a) o buffer atinge _FLUSH_LINHAS, ou (b) passou
172
+ # _FLUSH_SEG de borda. Custo honesto: um crash perde no máximo ~2 s
173
+ # de telemetria (aceitável — telemetria nunca é estado de treino;
174
+ # os checkpoints são independentes). fechar() descarrega o resto.
175
+ _FLUSH_LINHAS = 64
176
+ _FLUSH_SEG = 2.0
177
+
178
  def _gravar(self, linha: dict) -> None:
179
+ self._buffer.append(json.dumps(linha, ensure_ascii=False))
180
+ agora = time.time()
181
+ if (len(self._buffer) >= self._FLUSH_LINHAS
182
+ or (agora - self._ultimo_flush) >= self._FLUSH_SEG):
183
+ self._descarregar()
184
+
185
+ def _descarregar(self) -> None:
186
+ if not self._buffer:
187
+ self._ultimo_flush = time.time()
188
+ return
189
  with open(self.arquivo, "a", encoding="utf-8") as f:
190
+ f.write("\n".join(self._buffer) + "\n")
191
+ self._buffer.clear()
192
+ self._ultimo_flush = time.time()
193
+
194
+ def fechar(self) -> None:
195
+ """Descarrega o buffer pendente (chamar ao fim do treino/avaliação)."""
196
+ self._descarregar()
src/khtst/treino/cirurgia_grad.py CHANGED
@@ -18,6 +18,18 @@ homogeneidade (caminho do gradiente da auxiliar aproximadamente linear em
18
  sua perda), igualar escalares iguala ‖g_aux‖; sem homogeneidade, o cap
19
  garante o bound do pior caso na escala ESCALAR (o que controla λs).
20
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  Contrato: quem chama faz `optimizer.zero_grad()` antes e `optimizer.step()`
22
  depois — aqui só se escreve `p.grad`.
23
  """
@@ -89,7 +101,10 @@ def pcgrad_duas_perdas(modelo_ou_params, perda_main: torch.Tensor,
89
  n2_main += float((gm * gm).sum())
90
  projetado = dot < 0.0 and n2_main > eps
91
  with torch.no_grad():
92
- fator = -dot / n2_main if projetado else 0.0
 
 
 
93
  for p, gm, ga in zip(params, g_main, g_aux):
94
  if gm is None and ga is None:
95
  continue
@@ -98,7 +113,9 @@ def pcgrad_duas_perdas(modelo_ou_params, perda_main: torch.Tensor,
98
  elif ga is None:
99
  g = gm
100
  else:
101
- g = gm + (fator * ga if projetado else ga)
 
 
102
  p.grad = g.detach() if p.grad is None else p.grad + g.detach()
103
  return {"pcgrad/dot": dot, "pcgrad/projetado": float(projetado),
104
  "pcgrad/fator": fator}
 
18
  sua perda), igualar escalares iguala ‖g_aux‖; sem homogeneidade, o cap
19
  garante o bound do pior caso na escala ESCALAR (o que controla λs).
20
 
21
+ v23 (Teo 35.1 — auditoria independente, correção A-01): a implementação
22
+ original REESCALAVA g_aux por um escalar (g = gm + fator·ga, fator = −dot/n2)
23
+ em vez de PROJETAR — a garantia de não-piora do PCGrad (Yu et al., 2020) era
24
+ violada em 25,3% dos pares em conflito (medido; pior caso 1,73× o conflito
25
+ original). A versão correta projeta g_aux no complemento ortogonal de g_main:
26
+ g_aux′ = g_aux − (dot/n2)·g_main (o multiplicador é dot/n2, NEGATIVO
27
+ em conflito — a variante −dot/n2 com ga−fator·gm DOBRA o conflito:
28
+ ⟨g_aux′,g_main⟩ = 2·dot < 0).
29
+ Invariantes (prova no doc 35 §2): I1 ⟨g_aux′,g_main⟩=0 exato; I2 contração
30
+ ‖g_aux′‖²=‖g_aux‖²−dot²/n2; I3 ⟨g_total,g_main⟩=n2≥0 e ⟨g_total,g_aux′⟩=‖g_aux′‖²≥0.
31
+ Verificação numérica: 100.083 pares — 0 violações nas três.
32
+
33
  Contrato: quem chama faz `optimizer.zero_grad()` antes e `optimizer.step()`
34
  depois — aqui só se escreve `p.grad`.
35
  """
 
101
  n2_main += float((gm * gm).sum())
102
  projetado = dot < 0.0 and n2_main > eps
103
  with torch.no_grad():
104
+ # v23 (Teo 35.1): o multiplicador de g_main na projeção é (dot/n2) —
105
+ # NEGATIVO quando há conflito (dot<0). A forma antiga (−dot/n2 com
106
+ # fator·ga) reescalava g_aux e AMPLIFICAVA a direção conflitante.
107
+ fator = dot / n2_main if projetado else 0.0
108
  for p, gm, ga in zip(params, g_main, g_aux):
109
  if gm is None and ga is None:
110
  continue
 
113
  elif ga is None:
114
  g = gm
115
  else:
116
+ # v23: PROJEÇÃO de g_aux no complemento ortogonal de g_main
117
+ # (Yu et al., 2020, Alg. 2) — não reescala g_aux.
118
+ g = gm + (ga - fator * gm if projetado else ga)
119
  p.grad = g.detach() if p.grad is None else p.grad + g.detach()
120
  return {"pcgrad/dot": dot, "pcgrad/projetado": float(projetado),
121
  "pcgrad/fator": fator}
src/khtst/treino/treinador.py CHANGED
@@ -194,6 +194,12 @@ class TreinadorExtensao(MixinLR, MixinPerdas, MixinEstado, MixinFases,
194
  self.camadas_gsq = [m for m in modelo.modules()
195
  if isinstance(m, CamadaGSQRCO)]
196
  self.passo_global = 0
 
 
 
 
 
 
197
  self.epoca = 0
198
  self.t_ciclo = 0
199
  self.T_ciclo = cfg.treino.warmup + cfg.treino.janela_estagnacao * 4
@@ -706,10 +712,18 @@ class TreinadorExtensao(MixinLR, MixinPerdas, MixinEstado, MixinFases,
706
  # gradiente) e Var[ĝ_acc] = Var[∇L(lote físico)]/n_acc — mesma média,
707
  # ruído ↓ √n_acc, SEM o pico de ativações do lote físico grande.
708
  n_acc = self.lote_efetivo
 
 
 
 
 
 
 
 
709
  micro_final = (self.passo_global % n_acc) == (n_acc - 1)
710
- if n_acc > 1:
711
- perda_ce_total = perda_ce_total / n_acc
712
- perda_aux = perda_aux / n_acc if perda_aux is not None else None
713
  # backward: PCGrad (Teorema 10.10) ou soma simples
714
  if self.passo_global % n_acc == 0: # 1º micro do grupo
715
  self.otimizador.zero_grad(set_to_none=True)
@@ -775,6 +789,8 @@ class TreinadorExtensao(MixinLR, MixinPerdas, MixinEstado, MixinFases,
775
  for gp in self.otimizador.param_groups:
776
  gp["lr"] = lr
777
  self.otimizador.step()
 
 
778
  # v16 — amostra de telemetria + atualização dos controladores RM
779
  if (self.passo_global % self.autorreg.a_cada) == 0:
780
  frac_moe = None
@@ -1105,6 +1121,18 @@ class TreinadorExtensao(MixinLR, MixinPerdas, MixinEstado, MixinFases,
1105
  resultado[tarefa] = sum(perdas) / len(perdas)
1106
  if "lm" in resultado:
1107
  resultado["ppl_lm"] = math.exp(min(resultado["lm"], 10))
 
 
 
 
 
 
 
 
 
 
 
 
1108
  modelo.train()
1109
  return resultado
1110
 
 
194
  self.camadas_gsq = [m for m in modelo.modules()
195
  if isinstance(m, CamadaGSQRCO)]
196
  self.passo_global = 0
197
+ # v23 (Teo 35.9): tamanho real do grupo de acumulação corrente
198
+ # (nascido cheio; realinhado na retomada por retomar_estado).
199
+ # Derivado direto do cfg — `self.lote_efetivo` só é atribuído mais
200
+ # abaixo no __init__ (v14, §ensemble) e testes com cfg mínimo não
201
+ # passam por lá antes daqui (correção de integração da suíte v13/v16).
202
+ self._n_acc_atual = max(1, int(getattr(cfg.treino, "lote_efetivo", 1)))
203
  self.epoca = 0
204
  self.t_ciclo = 0
205
  self.T_ciclo = cfg.treino.warmup + cfg.treino.janela_estagnacao * 4
 
712
  # gradiente) e Var[ĝ_acc] = Var[∇L(lote físico)]/n_acc — mesma média,
713
  # ruído ↓ √n_acc, SEM o pico de ativações do lote físico grande.
714
  n_acc = self.lote_efetivo
715
+ # v23 (Teo 35.9, correção A-05): tamanho REAL do grupo corrente.
716
+ # Numa corrida fresca, `_n_acc_atual == n_acc` sempre (bit a bit
717
+ # igual ao v22). Numa RETOMADA no meio de um grupo (passo_global %
718
+ # n_acc = k ≠ 0), o grupo finalizado logo após a retomada teria só
719
+ # n_acc−k micros com perda dividida por n_acc — E[ĝ] enviesado por
720
+ # (n_acc−k)/n_acc. Dividir pelo tamanho real do grupo repara o
721
+ # estimador: E[ĝ] = ∇L(média dos micros do grupo) exatamente.
722
+ n_atual = max(1, int(getattr(self, "_n_acc_atual", n_acc)))
723
  micro_final = (self.passo_global % n_acc) == (n_acc - 1)
724
+ if n_atual > 1:
725
+ perda_ce_total = perda_ce_total / n_atual
726
+ perda_aux = perda_aux / n_atual if perda_aux is not None else None
727
  # backward: PCGrad (Teorema 10.10) ou soma simples
728
  if self.passo_global % n_acc == 0: # 1º micro do grupo
729
  self.otimizador.zero_grad(set_to_none=True)
 
789
  for gp in self.otimizador.param_groups:
790
  gp["lr"] = lr
791
  self.otimizador.step()
792
+ # v23 (Teo 35.9): grupo completo — o próximo volta ao tamanho cheio
793
+ self._n_acc_atual = self.lote_efetivo
794
  # v16 — amostra de telemetria + atualização dos controladores RM
795
  if (self.passo_global % self.autorreg.a_cada) == 0:
796
  frac_moe = None
 
1121
  resultado[tarefa] = sum(perdas) / len(perdas)
1122
  if "lm" in resultado:
1123
  resultado["ppl_lm"] = math.exp(min(resultado["lm"], 10))
1124
+ # v23 (correção A-22 da auditoria — honestidade): com corpus < 2
1125
+ # registros, por_tarefa é vazio e o resultado é {} — antes o gate de
1126
+ # fase sumia EM SILÊNCIO (sem métrica, sem sinal). Agora a ausência
1127
+ # é um ALERTA explícito no hub/eventos (visível no JSONL).
1128
+ if not resultado:
1129
+ if getattr(self, "hub", None) is not None:
1130
+ self.hub.alerta("avaliar/vazio", 0.0, self.passo_global,
1131
+ motivo="por_tarefa vazio (<2 registros) — "
1132
+ "gate de fase sem métricas")
1133
+ else:
1134
+ print("[avaliar] ALERTA (A-22): por_tarefa vazio (<2 "
1135
+ "registros) — gate de fase sem métricas", flush=True)
1136
  modelo.train()
1137
  return resultado
1138
 
src/khtst/treino/treinador_estado.py CHANGED
@@ -159,6 +159,13 @@ class MixinEstado:
159
  meta = self.checkpoints.meta_de(tag)
160
  if meta:
161
  self.passo_global = meta.get("passo_global", 0)
 
 
 
 
 
 
 
162
  self.epoca = meta.get("epoca", 0)
163
  self.t_ciclo = meta.get("t_ciclo", 0)
164
  self.T_ciclo = meta.get("T_ciclo", self.T_ciclo)
 
159
  meta = self.checkpoints.meta_de(tag)
160
  if meta:
161
  self.passo_global = meta.get("passo_global", 0)
162
+ # v23 (Teo 35.9, correção A-05): realinhar o tamanho do grupo de
163
+ # acumulação — retomada no meio de um grupo (fase k ≠ 0) termina
164
+ # o grupo com n_acc−k micros; dividir pelo tamanho REAL elimina
165
+ # o viés (n_acc−k)/n_acc do primeiro passo pós-retomada.
166
+ n_acc = int(getattr(self, "lote_efetivo", 1))
167
+ self._n_acc_atual = max(1, n_acc - (self.passo_global % n_acc)) \
168
+ if n_acc > 1 else 1
169
  self.epoca = meta.get("epoca", 0)
170
  self.t_ciclo = meta.get("t_ciclo", 0)
171
  self.T_ciclo = meta.get("T_ciclo", self.T_ciclo)
tests/conftest.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """conftest da suíte KHTST.
3
+
4
+ v23 (P12 da auditoria independente — EXECUTAVEL.md §8 fase 1): a suíte era
5
+ 100% torch-dependente — sem torch, os 20 arquivos falhavam NA COLEÇÃO por
6
+ import (20/20 erros, nenhuma rede de segurança). Aqui a ausência de torch é
7
+ detectada UMA vez e TODA a coleta vira skip com mensagem clara (pytest sai
8
+ 0 com "skipped", em vez de 20 erros de importação); com torch presente, a
9
+ coleta é idêntica à anterior (bit a bit — zero interferência).
10
+ """
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ import sys
15
+
16
+ _RAIZ = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17
+ for _caminho in (os.path.join(_RAIZ, "src"),
18
+ os.path.join(_RAIZ, "src", "khtst", "acelerado")):
19
+ if _caminho not in sys.path:
20
+ sys.path.insert(0, _caminho)
21
+
22
+ try: # torch é dependência dura da suíte
23
+ import torch # noqa: F401
24
+ _SEM_TORCH = False
25
+ except Exception: # ImportError e afins
26
+ _SEM_TORCH = True
27
+
28
+
29
+ def pytest_collection_modifyitems(config, items):
30
+ if not _SEM_TORCH:
31
+ return
32
+ import pytest
33
+ _skip = pytest.mark.skip(
34
+ reason="torch ausente neste ambiente (P12) — a suíte KHTST é "
35
+ "torch-dependente; instale torch para executar as provas")
36
+ for item in items:
37
+ item.add_marker(_skip)