Portuguese
mamba
mamba-3
ssm
muon
muonclip
qk-clip
adamw
optimizer
nope
hybrid-architecture
activation-checkpointing
3d-vision-language
depth-perception
point-cloud
diffusion
stable-diffusion
mixture-of-experts
kohonen
som-attention
random-forest
deep-neural-network
knowledge-distillation
bidirectional-distillation
engram-memory
multimodal
multi-token-prediction
dpo
rlhf
hh-rlhf
pcgrad
long-context
lra-qvit
engineer-agent
integral-state
cython
jev-classifiers
gumbel-softmax
quantization
riemannian-optimization
stiefel-manifold
cython-kernels
gradient-accumulation
vector-quantization
grayscale-detection
joint-perception
self-regulation
signal-table
abmo
ciar
growing-som
pt-br
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- .gitattributes +1 -0
- PLANO_KHMAMBAJEPA.md +21 -0
- README.md +60 -0
- RELATORIO_KHTST.md +46 -0
- auditoria_independente/CORRECAO_A01_PCGrad.md +86 -0
- auditoria_independente/EXECUTAVEL.md +281 -0
- auditoria_independente/PLANO_MELHORIAS_KHTST.md +557 -0
- auditoria_independente/README.md +30 -0
- auditoria_independente/modelo_ram.py +282 -0
- auditoria_independente/verif_cinza_lipschitz.py +144 -0
- auditoria_independente/verif_pcgrad_invariantes.py +135 -0
- auditoria_independente/verif_pcgrad_rope.py +127 -0
- configs/base.json +1 -1
- docs/matematica/26-gsq-rco-stiefel-riemanniano.md +17 -6
- docs/matematica/28-torchao-autoregulacao-mcjepa.md +17 -2
- docs/matematica/35-auditoria-independente-pcgrad-rope-eratas.md +322 -0
- graficos/v23/v23_evidencias.png +3 -0
- scripts/02_coletar_corpus.py +8 -13
- scripts/04_treinar.py +3 -0
- scripts/36_teste_v23.py +389 -0
- scripts/37_graficos_v23.py +163 -0
- scripts/38_publicar_v23.py +173 -0
- src/khtst/dados/checkpoints.py +46 -9
- src/khtst/dados/streaming.py +84 -16
- src/khtst/ipc/_fallback.py +13 -0
- src/khtst/ipc/khtst_ipc.c +0 -0
- src/khtst/ipc/khtst_ipc.cpython-312-x86_64-linux-gnu.so +2 -2
- src/khtst/ipc/khtst_ipc.pyx +11 -1
- src/khtst/mamba3/tronco.py +7 -2
- src/khtst/nucleo/modelo.py +10 -2
- src/khtst/nucleo/validacao.py +15 -1
- src/khtst/percepcao/atencao.py +59 -15
- src/khtst/percepcao/blocos.py +6 -4
- src/khtst/percepcao/vit_lra.py +10 -3
- src/khtst/servico/adhoc.py +5 -1
- src/khtst/telemetria/hub.py +31 -1
- src/khtst/treino/cirurgia_grad.py +19 -2
- src/khtst/treino/treinador.py +31 -3
- src/khtst/treino/treinador_estado.py +7 -0
- 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":
|
| 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)
|
| 127 |
-
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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_τ =
|
| 135 |
-
(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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^
|
| 218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
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 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
|
| 27 |
|
| 28 |
def main():
|
|
@@ -43,9 +44,7 @@ def main():
|
|
| 43 |
reg = json.loads(linha)
|
| 44 |
except Exception:
|
| 45 |
continue
|
| 46 |
-
chave =
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
f.write(dados)
|
| 81 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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": "
|
| 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 |
-
|
| 1012 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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:
|
| 3 |
-
size
|
|
|
|
| 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,
|
| 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 |
-
•
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
| 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 |
-
|
| 323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 327 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 333 |
if coletar is not None:
|
| 334 |
-
coletar["peso_arvore"] =
|
| 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
|
| 200 |
-
|
| 201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
| 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
|
| 711 |
-
perda_ce_total = perda_ce_total /
|
| 712 |
-
perda_aux = perda_aux /
|
| 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)
|