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 v19 — acoplamento JEPA↔encoders de produção (vistas de tokens com pesos compartilhados, gradiente exato no encoder real Teo 31.2, conselho renormalizado 31.3, alvo EMA pós-step com guarda 31.6) + SonicMoE no tronco opt-in (dispatch agrupado+tiles, equivalência sônico≡vetorial Δy≤7.1e-8 Teo 31.4, roofline honesto 31.5, pilha evitada 2.21MB/forward) + A/B/C PAREADO em dados reais 1015 registros com pareamento exato por RNG dedicado (Teo 31.7; ΔCE monótono em λ; JEPA acoplada −74%) + doc 31 (Teos 31.1–31.7) + suíte v19 34/34 + regressão v1–v18 zero; sem estados de treino; sem segredos
Browse files- .gitattributes +1 -0
- PLANO_KHMAMBAJEPA.md +36 -11
- README.md +30 -0
- RELATORIO_KHTST.md +70 -0
- configs/base.json +17 -0
- docs/matematica/31-v19-acoplamento-jepa-sonicmoe.md +269 -0
- graficos/v19/v19_evidencias.png +3 -0
- patches/jepa.py +29 -3
- scripts/24_teste_v19.py +401 -0
- scripts/25_graficos_v19.py +125 -0
- scripts/26_publicar_v19.py +185 -0
- src/khtst/acelerado/mc_jepa.c +1 -1
- src/khtst/acelerado/mc_jepa.cpython-312-x86_64-linux-gnu.so +1 -1
- src/khtst/acelerado/nucleos.c +0 -0
- src/khtst/acelerado/nucleos.cpython-312-x86_64-linux-gnu.so +2 -2
- src/khtst/acelerado/rco_gsq.c +0 -0
- src/khtst/acelerado/rco_gsq.cpython-312-x86_64-linux-gnu.so +2 -2
- src/khtst/config.py +12 -0
- src/khtst/jepa_familia/acoplamento.py +210 -0
- src/khtst/jepa_familia/base.py +9 -2
- src/khtst/nucleo/modelo.py +18 -0
- src/khtst/percepcao/moe.py +148 -3
- src/khtst/treino/treinador.py +62 -2
- tests/test_khtst_v19.py +207 -0
.gitattributes
CHANGED
|
@@ -49,3 +49,4 @@ src/khtst/acelerado/mc_jepa.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs
|
|
| 49 |
src/khtst/acelerado/rco_gsq.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 50 |
src/khtst/treino/__pycache__/treinador.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
|
| 51 |
src/khtst/ipc/khtst_ipc.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 49 |
src/khtst/acelerado/rco_gsq.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 50 |
src/khtst/treino/__pycache__/treinador.cpython-312.pyc filter=lfs diff=lfs merge=lfs -text
|
| 51 |
src/khtst/ipc/khtst_ipc.cpython-312-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 52 |
+
graficos/v19/v19_evidencias.png filter=lfs diff=lfs merge=lfs -text
|
PLANO_KHMAMBAJEPA.md
CHANGED
|
@@ -25,7 +25,7 @@ de parâmetros no treino E na inferência.
|
|
| 25 |
- Memória: SOM/GSOM/janela 1M indexada; difusão com motor de sonho
|
| 26 |
(ciclo fechado, v15.1).
|
| 27 |
|
| 28 |
-
## 2. Estado por subsistema (
|
| 29 |
|
| 30 |
| Subsistema | Estado | Prova/evidência |
|
| 31 |
|---|---|---|
|
|
@@ -45,19 +45,22 @@ de parâmetros no treino E na inferência.
|
|
| 45 |
| **λ_diff vivo (hipergradiente exato + âncora)** | ✅ v17 | Teo 29.2; λ* analítico ≡ numérico (5e-3) |
|
| 46 |
| **QE+CT com significância (Student-t/Bonferroni)** | ✅ v17 | Teo 29.5; 0/60 falsos-positivos; CT por coordenada |
|
| 47 |
| **Trade-off K com Ĉ estimado + unimodalidade** | ✅ v17 | Teos 29.8/29.9; K* fechado ≡ numérico ≤1%; teste do modelo por inclinação |
|
| 48 |
-
| SonicMoE (IO/tile-aware) |
|
| 49 |
-
| ReplaySSM |
|
| 50 |
-
| Image-JEPA/V-JEPA 2
|
| 51 |
-
| Audio-JEPA
|
| 52 |
| Brain-JEPA (fMRI/EEG) | ⏳ parcial | guarda do passo pronta (Teo 28.10); falta dataset |
|
| 53 |
-
| T-JEPA / LeJEPA / BJEPA | ⏳
|
| 54 |
| **v18 · robustez** | clamps Mamba-3 (δ,A), Muon wd 1e-4, NS fp32 adaptativo, PCGrad normalizado por EMA, τ GSQ 0.5/400 | Teos 30.2–30.6; regressão v1–v17 zero |
|
| 55 |
| **v18 · lógica** | validar_config (Teo 30.7), telemetria com níveis+rotação+histórico limitado (Teo 30.8), treinador em 4 mixins+facade | suíte v17 56✓ pós-refatoração |
|
| 56 |
| **v18 · IPC** | khtst/ipc: Rust (fonte) + Cython (produção, atomics C11) + fallback; 2 rings SPSC bidirecionais, SCM_RIGHTS, CRC32 | Teos 30.9/30.10; demo 2 processos 64 frames eco OK |
|
| 57 |
| **v18 · família JEPA** | 10 variantes (Image/V2/MC/Audio/Point/3D/Brain/T/Le/BJEPA) + ConselhoJepas multimodal | Teos 30.11/30.12; treino 5 passos L 1.19→0.82 |
|
| 58 |
| **v18 · SonicMoE/ReplaySSM** | dispatch agrupado+tiles+int8 com telemetria IO; reservoir de estados com re-injeção e drift | Teos 30.13/30.14; reservoir uniforme Monte-Carlo |
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
-
## 3. Matemática central (índice dos docs 00–
|
| 61 |
|
| 62 |
- **00–13**: SOM e variantes, atenção mista/KV, W8A8, MoE, microunidades,
|
| 63 |
escala por computo, janela 1M indexada.
|
|
@@ -141,7 +144,9 @@ hardware chinês (30.15) + regras R18.1–R18.10.
|
|
| 141 |
Image/V2/MC/Audio/Point/3D/Brain/T/Le/BJEPA + ConselhoJepas com gating
|
| 142 |
renormalizado e nascimento neutro; Teos 30.11/30.12; a integração das
|
| 143 |
variantes aos ENCODERS de produção do modelo (CodificadorImagem etc.)
|
| 144 |
-
permanece como evolução
|
|
|
|
|
|
|
| 145 |
- Image-JEPA (máscaras de blocos no espaço latente do CodificadorImagem);
|
| 146 |
- V-JEPA 2 (tubos espaço-temporais no CodificadorVideo — fonte
|
| 147 |
yaak-ai/L2D + 4DNeX quando em SSD);
|
|
@@ -154,15 +159,21 @@ hardware chinês (30.15) + regras R18.1–R18.10.
|
|
| 154 |
o hinge quando d_latente grande);
|
| 155 |
- BJEPA (bimodal imagem↔texto com predição cruzada latente).
|
| 156 |
Todas herdam a ponte Cython (mc_jepa.pyx) e a guarda anti-colapso.
|
| 157 |
-
4. **v19 —
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
softmax top-k do roteador em Cython/OpenMP (prioridade Cython do
|
| 159 |
requisito); detecção automática CPU/GPU mantida.
|
| 160 |
-
|
| 161 |
`AutorreguladorEstendido` (opt-in `treino.autorreg_v17.ativo`) com os
|
| 162 |
produtores de sinal reais (var do gradiente, taxa de trust do PRS,
|
| 163 |
razão L_moe/L_ce, ativação SOM, logit_max das NoPE) e corrida A/B
|
| 164 |
pareada antes de qualquer treino longo.
|
| 165 |
-
|
| 166 |
detecção NPU Ascend / MLU Cambricon / XPU / CUDA / MPS / CPU com
|
| 167 |
prioridade NPU/MLU e degradação honesta; Teo 30.15; toda a família
|
| 168 |
JEPA usa ops torch padrão → roda em todos os backends).
|
|
@@ -186,6 +197,20 @@ hardware chinês (30.15) + regras R18.1–R18.10.
|
|
| 186 |
atualizados TODA versão; redundâncias removidas.
|
| 187 |
---
|
| 188 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
## 7. Verificação v18 (resumo executável)
|
| 190 |
|
| 191 |
- Regressão v1–v17: TODA VERDE pós-refatoração (v1 25✓, v2 36✓, v3 ✓, v4 37✓,
|
|
|
|
| 25 |
- Memória: SOM/GSOM/janela 1M indexada; difusão com motor de sonho
|
| 26 |
(ciclo fechado, v15.1).
|
| 27 |
|
| 28 |
+
## 2. Estado por subsistema (v19)
|
| 29 |
|
| 30 |
| Subsistema | Estado | Prova/evidência |
|
| 31 |
|---|---|---|
|
|
|
|
| 45 |
| **λ_diff vivo (hipergradiente exato + âncora)** | ✅ v17 | Teo 29.2; λ* analítico ≡ numérico (5e-3) |
|
| 46 |
| **QE+CT com significância (Student-t/Bonferroni)** | ✅ v17 | Teo 29.5; 0/60 falsos-positivos; CT por coordenada |
|
| 47 |
| **Trade-off K com Ĉ estimado + unimodalidade** | ✅ v17 | Teos 29.8/29.9; K* fechado ≡ numérico ≤1%; teste do modelo por inclinação |
|
| 48 |
+
| **SonicMoE (IO/tile-aware)** | ✅ v18+v19 | doc 30 Teo 30.13 (módulo); doc 31 Teos 31.4/31.5 (tronco opt-in, equivalência Δy≤7.1e-8, pilha 2.21MB/forward) |
|
| 49 |
+
| **ReplaySSM** | ✅ v18 | doc 30 Teo 30.14; reservoir uniforme Monte-Carlo |
|
| 50 |
+
| **Image-JEPA/V-JEPA 2 ACOPLADAS aos encoders de produção** | ✅ v19 | doc 31 Teos 31.2/31.6; vistas de tokens compartilham pesos; perda −74% (96 passos reais) |
|
| 51 |
+
| **Audio-JEPA acoplada** (vista log-mel/convs) | ✅ v19 | doc 31 §3; 0.847→0.215 (−75%); Point/3D seguem no Mamba-3VL (§5) |
|
| 52 |
| Brain-JEPA (fMRI/EEG) | ⏳ parcial | guarda do passo pronta (Teo 28.10); falta dataset |
|
| 53 |
+
| T-JEPA / LeJEPA / BJEPA | ✅ v18 (standalone) / ⏳ acopladas | texto permanece no MotorJepa do tronco (doc 31 §1); iso/ciclo prontos |
|
| 54 |
| **v18 · robustez** | clamps Mamba-3 (δ,A), Muon wd 1e-4, NS fp32 adaptativo, PCGrad normalizado por EMA, τ GSQ 0.5/400 | Teos 30.2–30.6; regressão v1–v17 zero |
|
| 55 |
| **v18 · lógica** | validar_config (Teo 30.7), telemetria com níveis+rotação+histórico limitado (Teo 30.8), treinador em 4 mixins+facade | suíte v17 56✓ pós-refatoração |
|
| 56 |
| **v18 · IPC** | khtst/ipc: Rust (fonte) + Cython (produção, atomics C11) + fallback; 2 rings SPSC bidirecionais, SCM_RIGHTS, CRC32 | Teos 30.9/30.10; demo 2 processos 64 frames eco OK |
|
| 57 |
| **v18 · família JEPA** | 10 variantes (Image/V2/MC/Audio/Point/3D/Brain/T/Le/BJEPA) + ConselhoJepas multimodal | Teos 30.11/30.12; treino 5 passos L 1.19→0.82 |
|
| 58 |
| **v18 · SonicMoE/ReplaySSM** | dispatch agrupado+tiles+int8 com telemetria IO; reservoir de estados com re-injeção e drift | Teos 30.13/30.14; reservoir uniforme Monte-Carlo |
|
| 59 |
+
| **v19 · acoplamento JEPA↔encoders** | vistas de tokens compartilham pesos dos encoders REAIS; conselho renormalizado; alvo EMA pós-step com guarda | Teos 31.1–31.3/31.6; gradiente exato no encoder (drift 8.3e-4 > 7.0e-4); perda −74% |
|
| 60 |
+
| **v19 · SonicMoE no tronco (opt-in)** | forward_sonico (dispatch agrupado + tiles + telemetria); gate fonte única | Teos 31.1/31.4/31.5; Δy ≤ 7.1e-8 em produção; pilha evitada 2.21 MB/forward; wallclock CPU 0.83–0.875× (honesto) |
|
| 61 |
+
| **v19 · A/B/C pareado real** | 3 braços, 96 passos, corpus 1015 registros reais (395 frescos), pareamento por RNG dedicado | Teo 31.7; ΔCE monótono em λ (+0.090→+0.046); graficos/v19/v19_evidencias.png |
|
| 62 |
|
| 63 |
+
## 3. Matemática central (índice dos docs 00–31)
|
| 64 |
|
| 65 |
- **00–13**: SOM e variantes, atenção mista/KV, W8A8, MoE, microunidades,
|
| 66 |
escala por computo, janela 1M indexada.
|
|
|
|
| 144 |
Image/V2/MC/Audio/Point/3D/Brain/T/Le/BJEPA + ConselhoJepas com gating
|
| 145 |
renormalizado e nascimento neutro; Teos 30.11/30.12; a integração das
|
| 146 |
variantes aos ENCODERS de produção do modelo (CodificadorImagem etc.)
|
| 147 |
+
permanece como evolução v20 — o ACOPLAMENTO das 3 modalidades
|
| 148 |
+
core (imagem/áudio/vídeo) foi ENTREGUE na v19 via vistas de tokens
|
| 149 |
+
compartilhadas; doc 31):
|
| 150 |
- Image-JEPA (máscaras de blocos no espaço latente do CodificadorImagem);
|
| 151 |
- V-JEPA 2 (tubos espaço-temporais no CodificadorVideo — fonte
|
| 152 |
yaak-ai/L2D + 4DNeX quando em SSD);
|
|
|
|
| 159 |
o hinge quando d_latente grande);
|
| 160 |
- BJEPA (bimodal imagem↔texto com predição cruzada latente).
|
| 161 |
Todas herdam a ponte Cython (mc_jepa.pyx) e a guarda anti-colapso.
|
| 162 |
+
4. **v19 ✓ — Acoplamento JEPA↔encoders + SonicMoE no tronco (ENTREGUE,
|
| 163 |
+
doc 31)**: vistas de tokens compartilhadas (Teo 31.2), conselho
|
| 164 |
+
renormalizado (31.3), equivalência sônica (31.4), roofline honesto
|
| 165 |
+
(31.5), guarda por variante (31.6), A/B/C pareado real (31.7) —
|
| 166 |
+
resultados no RELATORIO §18; SonicMoE tronco opt-in com leitura
|
| 167 |
+
honesta (IO 2.0× modelado, wallclock CPU 0.83–0.875×).
|
| 168 |
+
5. **v20 — Cython total dos hot-paths**: conv1d seletiva do Mamba-3 e
|
| 169 |
softmax top-k do roteador em Cython/OpenMP (prioridade Cython do
|
| 170 |
requisito); detecção automática CPU/GPU mantida.
|
| 171 |
+
6. **v20 — Integração da tabela v17 no treinador**: ligar
|
| 172 |
`AutorreguladorEstendido` (opt-in `treino.autorreg_v17.ativo`) com os
|
| 173 |
produtores de sinal reais (var do gradiente, taxa de trust do PRS,
|
| 174 |
razão L_moe/L_ce, ativação SOM, logit_max das NoPE) e corrida A/B
|
| 175 |
pareada antes de qualquer treino longo.
|
| 176 |
+
7. **v18 ✓ — Hardware chinês** (ENTREGUE: `jepa_familia/dispositivo.py` —
|
| 177 |
detecção NPU Ascend / MLU Cambricon / XPU / CUDA / MPS / CPU com
|
| 178 |
prioridade NPU/MLU e degradação honesta; Teo 30.15; toda a família
|
| 179 |
JEPA usa ops torch padrão → roda em todos os backends).
|
|
|
|
| 197 |
atualizados TODA versão; redundâncias removidas.
|
| 198 |
---
|
| 199 |
|
| 200 |
+
## 6b. Verificação v19 (resumo executável)
|
| 201 |
+
|
| 202 |
+
- Suíte v19 NOVA: **34/34 verdes** (Teos 31.1–31.7 citados em cada
|
| 203 |
+
verificação: gate bit-idêntico, equivalência sônica de saída/gradientes,
|
| 204 |
+
telemetria IO honesta, gradiente no encoder REAL, alvo EMA sem grad,
|
| 205 |
+
RuntimeError da guarda, RNG dedicado do pareamento, nascimento neutro,
|
| 206 |
+
config opt-in, doc 31 presente).
|
| 207 |
+
- verificar_tudo: **49✓/0✗** (após sincronizar patches/jepa.py —
|
| 208 |
+
dessincronização pré-existente da v18 detectada pelo próprio hash).
|
| 209 |
+
- Regressão v1–v18: TODA VERDE (25/36/✓/37/32/48/44/21/✓/✓/28/27/40/—/✓/
|
| 210 |
+
56/46).
|
| 211 |
+
- A/B/C real (96 passos × 3 braços, corpus 1015 reais): ver RELATORIO §18
|
| 212 |
+
e graficos/v19/v19_evidencias.png.
|
| 213 |
+
|
| 214 |
## 7. Verificação v18 (resumo executável)
|
| 215 |
|
| 216 |
- Regressão v1–v17: TODA VERDE pós-refatoração (v1 25✓, v2 36✓, v3 ✓, v4 37✓,
|
README.md
CHANGED
|
@@ -95,6 +95,33 @@ gradiente**. Provas em `docs/matematica/27`.
|
|
| 95 |
> --teto_horas 9.0 --continuar_ate_horas 8.0 --estados_hf`).
|
| 96 |
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
## O que há de novo no v18 — ROBUSTEZ · IPC MODELO→MODELO · FAMÍLIA JEPA + SONICMOE + REPLAYSSM (provas em `docs/matematica/30`, memória em `PLANO_KHMAMBAJEPA.md`)
|
| 99 |
|
| 100 |
- **Robustez provada (doc 30, Teos 30.1–30.6)**: clamps de sanidade no
|
|
@@ -273,6 +300,9 @@ imagem_caption · asr · traducao · raciocinio · classificacao`
|
|
| 273 |
|
| 274 |
## Histórico
|
| 275 |
|
|
|
|
|
|
|
|
|
|
| 276 |
- **v18** — robustez provada (Teos 30.1–30.8), IPC Modelo→Modelo
|
| 277 |
Rust/Cython (Teos 30.9–30.10), família JEPA 10 variantes + SonicMoE +
|
| 278 |
ReplaySSM + hardware chinês (Teos 30.11–30.15); suíte v18 46/46,
|
|
|
|
| 95 |
> --teto_horas 9.0 --continuar_ate_horas 8.0 --estados_hf`).
|
| 96 |
|
| 97 |
|
| 98 |
+
## O que há de novo no v19 — ACOPLAMENTO JEPA↔ENCODERS DE PRODUÇÃO (A/B pareado) · SONICMOE NO TRONCO (opt-in) · DATASETS REAIS (provas em `docs/matematica/31`, memória em `PLANO_KHMAMBAJEPA.md`)
|
| 99 |
+
|
| 100 |
+
- **JEPA acoplada aos encoders REAIS** (`jepa_familia/acoplamento.py`):
|
| 101 |
+
vistas de tokens que COMPARTILHAM os pesos de `CodificadorImagem/Audio/
|
| 102 |
+
Video` — o gradiente da auto-supervisão treina o encoder que o modelo
|
| 103 |
+
realmente usa (Teo 31.2 — regra da cadeia exata; sem gêmeo paralelo).
|
| 104 |
+
Alvo EMA pós-step com guarda de ordem por variante (Teo 31.6) e conselho
|
| 105 |
+
renormalizado com nascimento neutro (Teo 31.3). Opt-in:
|
| 106 |
+
`treino.jepa_acoplado.lambda` (default 0 — v18 exato, Teo 31.1).
|
| 107 |
+
- **SonicMoE no TRONCO** (opt-in `modelo.sonicmoe.ativo`): execução com
|
| 108 |
+
dispatch agrupado por expert + GEMM em tiles + telemetria IO; gate é
|
| 109 |
+
fonte única (`_computar_gate`) — equivalência provada e medida
|
| 110 |
+
(Teo 31.4: Δy ≤ 7.1e-8 em produção, Δgrad ≤ 2.1e-7, gate bit-idêntico);
|
| 111 |
+
roofline HONESTO (Teo 31.5): ganho de IO de pesos (pilha evitada
|
| 112 |
+
2.21 MB/forward, 2.0× modelado) — wallclock CPU nesta escala 0.83–0.875×
|
| 113 |
+
(reportado como está).
|
| 114 |
+
- **A/B/C PAREADO em dados reais** (Teo 31.7): 3 braços × 96 passos,
|
| 115 |
+
corpus REAL de 1015 registros (395 frescos de LeRobot/GSM8K/MATH-500/
|
| 116 |
+
hendrycks/OpenR1/IFM), pareamento EXATO por re-seed pós-construção +
|
| 117 |
+
gerador dedicado nas máscaras + conselho em eval. Resultados: baseline
|
| 118 |
+
1.606× · v19 λ=0.3 1.545× (ΔCE +0.090) · v19 λ=0.1 1.573× (ΔCE +0.046)
|
| 119 |
+
— monótono em λ (Cor. 31.7); JEPA acoplada APRENDE (perda −74%);
|
| 120 |
+
encoders recebem o gradiente extra (drift 8.3e-4 vs 7.0e-4).
|
| 121 |
+
- **Verificação**: suíte v19 34/34 verdes · verificar_tudo 49✓/0✗ ·
|
| 122 |
+
regressão v1–v18 TODA VERDE · evidências em
|
| 123 |
+
`graficos/v19/v19_evidencias.png` e `telemetria_out/v19_relatorio_ab.json`.
|
| 124 |
+
|
| 125 |
## O que há de novo no v18 — ROBUSTEZ · IPC MODELO→MODELO · FAMÍLIA JEPA + SONICMOE + REPLAYSSM (provas em `docs/matematica/30`, memória em `PLANO_KHMAMBAJEPA.md`)
|
| 126 |
|
| 127 |
- **Robustez provada (doc 30, Teos 30.1–30.6)**: clamps de sanidade no
|
|
|
|
| 300 |
|
| 301 |
## Histórico
|
| 302 |
|
| 303 |
+
- **v19** — acoplamento JEPA↔encoders de produção com A/B/C pareado
|
| 304 |
+
(Teos 31.1–31.7), SonicMoE no tronco opt-in com equivalência provada,
|
| 305 |
+
dados reais 1015 registros; suíte v19 34/34, regressão zero.
|
| 306 |
- **v18** — robustez provada (Teos 30.1–30.8), IPC Modelo→Modelo
|
| 307 |
Rust/Cython (Teos 30.9–30.10), família JEPA 10 variantes + SonicMoE +
|
| 308 |
ReplaySSM + hardware chinês (Teos 30.11–30.15); suíte v18 46/46,
|
RELATORIO_KHTST.md
CHANGED
|
@@ -965,3 +965,73 @@ ReplaySSM em hardware chinês/CPU, plano Markdown com provas e memória .md.
|
|
| 965 |
**Verificação**: suíte v18 NOVA — **46/46 verdes** (doc 30 citado em cada
|
| 966 |
verificação); regressão v1–v17 TODA VERDE (25/36/✓/37/32/48/44/21/✓/✓/28/
|
| 967 |
27/40/—/✓/56); demo IPC 2 processos aprovada.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 965 |
**Verificação**: suíte v18 NOVA — **46/46 verdes** (doc 30 citado em cada
|
| 966 |
verificação); regressão v1–v17 TODA VERDE (25/36/✓/37/32/48/44/21/✓/✓/28/
|
| 967 |
27/40/—/✓/56); demo IPC 2 processos aprovada.
|
| 968 |
+
|
| 969 |
+
## 18. v19 — Acoplamento JEPA↔encoders de produção (A/B pareado) + SonicMoE no tronco (opt-in) + datasets reais
|
| 970 |
+
|
| 971 |
+
**Entrada**: requisito v19 — "acoplar as variantes JEPA aos encoders de
|
| 972 |
+
produção com A/B pareado; ativar SonicMoE no tronco (opt-in) e rodar testes
|
| 973 |
+
de datasets reais para averiguar funcionalidades". Fonte: doc 31 (Teos
|
| 974 |
+
31.1–31.7 + regras R19.1–R19.8).
|
| 975 |
+
|
| 976 |
+
**Acoplamento JEPA↔encoders de produção (doc 31 §§1–3, Teos 31.1–31.3):**
|
| 977 |
+
- `jepa_familia/acoplamento.py` (NOVO): vistas de tokens `VistaTokensImagem/
|
| 978 |
+
Audio/Video` que chamam os MÓDULOS do encoder de produção SEM cópia
|
| 979 |
+
(proj/pos/cls/blocos/bigru/norm; log-mel/convs; CLS por frame) —
|
| 980 |
+
**Teo 31.2**: o gradiente JEPA é a regra da cadeia EXATA sobre os pesos
|
| 981 |
+
reais do encoder (autografa por identidade de tensor); **31.2-cor**: os
|
| 982 |
+
parâmetros do encoder ficam FORA do grupo extra AdamW (filtro por
|
| 983 |
+
identidade — registrá-los duas vezes aplicaria dois passos ao mesmo θ).
|
| 984 |
+
- Alvo EMA = deepcopy no nascimento (θ̃←m·θ̃+(1−m)·θ APÓS optimizer.step());
|
| 985 |
+
**Teo 31.6**: guarda de ordem EMA por variante (tolerância = lote_efetivo,
|
| 986 |
+
RuntimeError na violação — mesmo contrato do MotorJepa/Teo 30.1).
|
| 987 |
+
- **Teo 31.3**: conselho renormalizado = média convexa com nascimento neutro
|
| 988 |
+
(gating softmax uniforme no nascimento; variantes sem lote no passo são
|
| 989 |
+
excluídas e os pesos renormalizados).
|
| 990 |
+
- Integração no treinador: perda do conselho via `_aux_norm` (Teo 30.3
|
| 991 |
+
anti-dominação), parâmetros novos no PCGrad, telemetria `jepa_ac/*`.
|
| 992 |
+
|
| 993 |
+
**SonicMoE no tronco (doc 31 §§5, Teos 31.4–31.5, opt-in):**
|
| 994 |
+
- Gate extraído para `_computar_gate` (refatoração VERBATIM — Teo 31.1:
|
| 995 |
+
gate bit-idêntico, Δg=0.000e+00 medido).
|
| 996 |
+
- `forward_sonico`: dispatch agrupado por expert + GEMM em tiles +
|
| 997 |
+
telemetria IO. **Teo 31.4**: equivalência sônico≡vetorial (saída,
|
| 998 |
+
gradientes, L_lb/L_ort/_Z_ultimo) — medido: Δy=3.0e-08, Δgrad_x=3.4e-08,
|
| 999 |
+
Δgrad_w=2.1e-07, ΔL_lb=0.
|
| 1000 |
+
- **Teo 31.5 (roofline HONESTO)**: com o gate foco atual (softmax sobre os
|
| 1001 |
+
ativos), Σ_e n_e = A·BT — fator_trabalho=1; o ganho real é de IO de
|
| 1002 |
+
PESOS (pilha `torch.stack` evitada: 2.21 MB/forward; ganho modelado
|
| 1003 |
+
2.0×). Wallclock CPU medido: 0.83–0.875× (sônico mais lento nesta
|
| 1004 |
+
escala — overhead do laço em GEMMs pequenos; reportado como está).
|
| 1005 |
+
31.5-nota: se o gate tornar-se esparso por token, o mesmo código passa a
|
| 1006 |
+
omitir linhas automaticamente.
|
| 1007 |
+
- Wiring: `modelo.sonicmoe {ativo, tile_m}` (default DESLIGADO) — nasce
|
| 1008 |
+
neutro (Teo 31.1); atua só na fase FOCO (R19.7).
|
| 1009 |
+
|
| 1010 |
+
**A/B/C PAREADO em datasets reais (doc 31 §§7/9, Teo 31.7):**
|
| 1011 |
+
- **Pareamento EXATO**: re-seed pós-construção + máscaras JEPA com
|
| 1012 |
+
gerador DEDICADO + conselho em eval — o fluxo RNG global dos braços é
|
| 1013 |
+
idêntico ⟹ mesmos lotes; ΔCE_t pareado por passo (Teo 31.7).
|
| 1014 |
+
- Corpus REAL 1015 registros (395 FRESCOS: LeRobot fetchman, GSM8K,
|
| 1015 |
+
MATH-500, hendrycks, OpenR1, IFM socratic — orçamento por fonte),
|
| 1016 |
+
protocolo 96 passos × 3 braços com retomada por checkpoint (RAM 4 GiB;
|
| 1017 |
+
OOM da 1ª tentativa com lote_efetivo=4 documentado e corrigido).
|
| 1018 |
+
- Resultados: A baseline 9.738→6.064 (1.606×) · B v19 λ=0.3 9.726→6.296
|
| 1019 |
+
(1.545×, ΔCE=+0.090) · C v19 λ=0.1 9.732→6.187 (1.573×, ΔCE=+0.046) —
|
| 1020 |
+
**monótono em λ** (Cor. 31.7, Teo 28.7-ii). JEPA acoplada APRENDE:
|
| 1021 |
+
0.808→0.205 (−74%) e 0.847→0.215 (−75%); drift dos encoders com JEPA
|
| 1022 |
+
(8.26e-4/8.75e-4 imagem) > baseline (7.04e-4) — gradiente extra CHEGA
|
| 1023 |
+
ao encoder real.
|
| 1024 |
+
- Gráficos: `graficos/v19/v19_evidencias.png` (3 painéis).
|
| 1025 |
+
|
| 1026 |
+
**Correção de processo**: `patches/jepa.py` estava DIVERGENTE da produção
|
| 1027 |
+
(a guarda Teo 30.1 da v18 não foi espelhada) — hash SHA-256 dessincronizado
|
| 1028 |
+
detectado pelo `verificar_tudo` (48✓/1✗); sincronizado ⟹ 49✓/0✗.
|
| 1029 |
+
|
| 1030 |
+
**Verificação**: suíte v19 NOVA — **34/34 verdes** (doc 31 citado em cada
|
| 1031 |
+
verificação); verificar_tudo **49✓/0✗**; regressão v1–v18 TODA VERDE
|
| 1032 |
+
(25/36/✓/37/32/48/44/21/✓/✓/28/27/40/—/✓/56/46).
|
| 1033 |
+
|
| 1034 |
+
**O que NÃO afirmamos** (doc 31 §10): benefício de wall-clock CPU do
|
| 1035 |
+
SonicMoE nesta escala; benefício de CE em 96 passos (o acoplamento custa
|
| 1036 |
+
≤ +0.09 de CE — benefícios de representação esperados em corrida longa/
|
| 1037 |
+
probes downstream); economia de linhas com o gate atual (fator_trabalho=1).
|
configs/base.json
CHANGED
|
@@ -83,6 +83,10 @@
|
|
| 83 |
"lambda_mamba3": 0.25,
|
| 84 |
"ativo_vl": true,
|
| 85 |
"n_camadas_vl": 6
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
}
|
| 87 |
},
|
| 88 |
"treino": {
|
|
@@ -198,6 +202,19 @@
|
|
| 198 |
"alpha": 1.0,
|
| 199 |
"eta": 0.05,
|
| 200 |
"tau_kd": 2.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
}
|
| 202 |
},
|
| 203 |
"ciclo": {
|
|
|
|
| 83 |
"lambda_mamba3": 0.25,
|
| 84 |
"ativo_vl": true,
|
| 85 |
"n_camadas_vl": 6
|
| 86 |
+
},
|
| 87 |
+
"sonicmoe": {
|
| 88 |
+
"ativo": false,
|
| 89 |
+
"tile_m": 64
|
| 90 |
}
|
| 91 |
},
|
| 92 |
"treino": {
|
|
|
|
| 202 |
"alpha": 1.0,
|
| 203 |
"eta": 0.05,
|
| 204 |
"tau_kd": 2.0
|
| 205 |
+
},
|
| 206 |
+
"jepa_acoplado": {
|
| 207 |
+
"lambda": 0.0,
|
| 208 |
+
"variantes": [
|
| 209 |
+
"imagem",
|
| 210 |
+
"audio",
|
| 211 |
+
"video"
|
| 212 |
+
],
|
| 213 |
+
"ema_momentum": 0.996,
|
| 214 |
+
"peso_var": 0.01,
|
| 215 |
+
"peso_iso": 0.0,
|
| 216 |
+
"pred_hidden": 256,
|
| 217 |
+
"aprende_gate": false
|
| 218 |
}
|
| 219 |
},
|
| 220 |
"ciclo": {
|
docs/matematica/31-v19-acoplamento-jepa-sonicmoe.md
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 31 — v19: Acoplamento JEPA↔Encoders de Produção e SonicMoE no Tronco
|
| 2 |
+
|
| 3 |
+
**KHMAMBAJEPA — rodada v19** (requisito: "acoplar as variantes JEPA aos
|
| 4 |
+
encoders de produção com A/B pareado; ativar SonicMoE no tronco (opt-in) e
|
| 5 |
+
rodar testes de datasets reais para averiguar funcionalidades").
|
| 6 |
+
|
| 7 |
+
Fontes: `src/khtst/jepa_familia/acoplamento.py` (novo),
|
| 8 |
+
`src/khtst/percepcao/moe.py` (gate extraído + `forward_sonico`),
|
| 9 |
+
`src/khtst/treino/treinador.py` (integração), `src/khtst/config.py` +
|
| 10 |
+
`configs/base.json` (chaves opt-in), `scripts/24_teste_v19.py` (A/B/C real).
|
| 11 |
+
Suíte: `tests/test_khtst_v19.py`.
|
| 12 |
+
|
| 13 |
+
Regras operacionais: R19.1–R19.8 (§8).
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
## 1. Problema e arquitetura (§2 do requisito)
|
| 18 |
+
|
| 19 |
+
Na v18 a família JEPA (10 variantes) tinha STEMS PRÓPRIOS (convs pequenos): a
|
| 20 |
+
auto-supervisão treinava esses stems paralelos, não os encoders que o modelo
|
| 21 |
+
realmente usa (`CodificadorImagem/Audio/Video`). A v19 fecha o laço: cada
|
| 22 |
+
variante acoplada consome uma **VISTA DE TOKENS** do encoder de produção
|
| 23 |
+
compartilhando os pesos — o gradiente JEPA flui **para o encoder real**.
|
| 24 |
+
|
| 25 |
+
```
|
| 26 |
+
online: z = Backbone_j(vista_j(θ_enc, x)) ← usa os MÓDULOS do encoder
|
| 27 |
+
alvo: z̃ = Backbone_j(vista_j(θ̃_enc, x)) ← deepcopy EMA (sem grad)
|
| 28 |
+
perda: L = MSE(P(z_ctx), sg(z̃_tgt)) + β·hinge_var + γ·iso
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
## 2. Teo 31.1 (nascimento neutro do acoplamento e do SonicMoE)
|
| 32 |
+
|
| 33 |
+
**Afirmação.** Com `treino.jepa_acoplado.lambda = 0` (ou chave ausente) e
|
| 34 |
+
`modelo.sonicmoe.ativo = false` (ou chave ausente), a trajetória de treino é
|
| 35 |
+
a v18 EXATA: nenhum parâmetro novo, nenhum termo de perda novo, nenhum
|
| 36 |
+
consumo de RNG novo.
|
| 37 |
+
|
| 38 |
+
**Prova.** Construtiva, por inspeção do fluxo:
|
| 39 |
+
(i) o conselho só é construído sob `float(cfg_jac.get("lambda",0)) > 0`
|
| 40 |
+
(treinador, bloco v19) — com λ=0, `conselho_acoplado is None` ⟹ zero
|
| 41 |
+
parâmetros novos no otimizador (`params_jac=[]`), zero termos em
|
| 42 |
+
`auxiliares` (o ramo `if self.conselho_acoplado is not None` não dispara) e
|
| 43 |
+
zero chamadas de `passo_pos_backward`. (ii) `KHTSTModel.__init__` só liga
|
| 44 |
+
`modo_sonico` sob `cfg_sonic.get("ativo")` — sem a chave, `MoEAgrupavel
|
| 45 |
+
.modo_sonico=False` (valor no `__init__`) e `forward` segue o caminho
|
| 46 |
+
vetorial v8 linha a linha. (iii) A extração do gate para
|
| 47 |
+
`_computar_gate` é refatoração VERBATIM (mesmas operações, mesma ordem —
|
| 48 |
+
diff de código): com os mesmos pesos, o gate é bit-idêntico (medido:
|
| 49 |
+
Δg=0.000e+00 na suíte v19). ∎
|
| 50 |
+
|
| 51 |
+
**Verificação empírica**: suíte v19 (gate bit-idêntico; `sonicmoe_ativo
|
| 52 |
+
is False` por padrão); regressão v1–v18 toda verde (o refator não mudou
|
| 53 |
+
nada na prática do caminho antigo).
|
| 54 |
+
|
| 55 |
+
## 3. Teo 31.2 (gradiente do acoplamento é o gradiente exato do encoder)
|
| 56 |
+
|
| 57 |
+
**Afirmação.** Seja `vista_j(θ_enc, ·)` a vista que chama os módulos do
|
| 58 |
+
encoder de produção (sem cópia). Então para qualquer perda L que dependa de
|
| 59 |
+
θ_enc apenas via `z = Backbone(vista(θ_enc, x))`:
|
| 60 |
+
|
| 61 |
+
∂L/∂θ_enc = (∂z/∂vista) · (∂vista/∂θ_enc) — regra da cadeia EXATA,
|
| 62 |
+
|
| 63 |
+
ou seja, a perda JEPA treina os MESMOS parâmetros que a perda CE (via
|
| 64 |
+
prefixo multimodal); os dois gradientes somam-se no otimizador
|
| 65 |
+
(PCGrad resolve conflitos — Teo 10.10/30.3).
|
| 66 |
+
|
| 67 |
+
**Prova.** `vista_j` é um `nn.Module` cujo `forward` chama
|
| 68 |
+
`self.enc.proj/blocos/bigru/norm` — módulos REFERENCIADOS (não copiados), e
|
| 69 |
+
PyTorch autografa por identidade de `Tensor`, não por caminho de chamada.
|
| 70 |
+
Portanto o grafo da perda JEPA contém os nós das operações aplicadas aos
|
| 71 |
+
tensores de θ_enc, e o autograd acumula em `θ_enc.grad` a contribuição
|
| 72 |
+
exata da cadeia acima. Como `θ_enc` também está no grafo da CE (encoder
|
| 73 |
+
alimenta o prefixo), os dois termos acumulam no mesmo `.grad` — linearidade
|
| 74 |
+
do gradiente. **Cor (31.2-cor, bug de integração evitado por construção):**
|
| 75 |
+
os parâmetros do encoder NÃO podem ir ao grupo `extra_adam` do otimizador —
|
| 76 |
+
eles já pertencem aos grupos principais; registrá-los duas vezes aplicaria
|
| 77 |
+
dois passos ao mesmo θ. A integração filtra `id(p) ∉ id(modelo.parameters())`
|
| 78 |
+
e só envia os parâmetros NOVOS (backbones/predictors/gate do conselho). ∎
|
| 79 |
+
|
| 80 |
+
**Verificação empírica** (suíte v19): `perda` do conselho com lote de imagem
|
| 81 |
+
produz `enc_imagem.proj.weight.grad ≠ 0`; alvo EMA com
|
| 82 |
+
`requires_grad=False` para todos os parâmetros; treino real (96 passos,
|
| 83 |
+
corpus de 1015 registros): drift do encoder com JEPA (8.26e-4) > drift sem
|
| 84 |
+
(7.04e-4) — gradiente adicional REAL chegando ao encoder.
|
| 85 |
+
|
| 86 |
+
**Corolário 31.2-a (bias EMA).** O alvo é θ̃_t = EMA(θ) com m=0.996; o
|
| 87 |
+
gradiente do predictor vê um alvo "quase-estacionário": o viés relativo é
|
| 88 |
+
O((1−m)·η·G) por passo (mesma análise do Teo 30.1) — controlado pela guarda
|
| 89 |
+
de ordem (Teo 31.6).
|
| 90 |
+
|
| 91 |
+
## 4. Teo 31.3 (conselho renormalizado é média convexa com nascimento neutro)
|
| 92 |
+
|
| 93 |
+
**Afirmação.** A perda do conselho
|
| 94 |
+
`L = Σ_m w_m·L_m / Σ_m w_m` (renormalização sobre as modalidades presentes)
|
| 95 |
+
é média CONVEXA das perdas das variantes ativas, com w_m = softmax(logits)
|
| 96 |
+
— no nascimento, w_m = 1/M (logits zero).
|
| 97 |
+
|
| 98 |
+
**Prova.** softmax é positivo e soma 1; a renormalização sobre o
|
| 99 |
+
subconjunto ativo A preserva `Σ_{m∈A} w'_m = 1` com w'_m ≥ 0 — média
|
| 100 |
+
convexa ⟹ L entre min L_m e max L_m (nunca amplifica); gradiente:
|
| 101 |
+
∂L/∂L_m = w'_m (pesos de contribuição exatos). Com logits zerados,
|
| 102 |
+
w_m = 1/M ⟹ nascimento neutro (Teo 21.12 reusado). ∎
|
| 103 |
+
|
| 104 |
+
## 5. SonicMoE no tronco (Teos 31.4–31.5)
|
| 105 |
+
|
| 106 |
+
### Teo 31.4 (equivalência sônico ≡ vetorial)
|
| 107 |
+
|
| 108 |
+
**Afirmação.** Para o MESMO gate `g` (mesma semente, mesmos pesos) e fase
|
| 109 |
+
foco, o caminho `forward_sonico` produz
|
| 110 |
+
|
| 111 |
+
y_sonico[t] = (Σ_e g_e[t]·FFN_e(x_t)) / Σ_e g_e[t] = y_vetorial[t]
|
| 112 |
+
|
| 113 |
+
com desvio apenas de ordem de acumulação em ponto flutuante; o MESMO vale
|
| 114 |
+
para os gradientes em relação a x e a todos os pesos de expert, e para a
|
| 115 |
+
telemetria (`_Z_ultimo`, `_g_ultimo`, L_lb, L_ort).
|
| 116 |
+
|
| 117 |
+
**Prova.** (i) Gate: ambas as rotas chamam `_computar_gate` (fonte única;
|
| 118 |
+
extração verbatim — Teo 31.1) ⟹ g bit-idêntico. (ii) Suporte: no caminho
|
| 119 |
+
vetorial, `idx_at = {e : ∃t g_e[t]>0}` e `YE` é computado para (e,t) com
|
| 120 |
+
g_e[t] possivelmente nulo; a soma final multiplica por g_e[t], então termos
|
| 121 |
+
nulos não contribuem. O caminho sônico computa exatamente os pares com
|
| 122 |
+
g_e[t]>0 (`linhas = (cols>0)`). Como o gate foco é softmax sobre os ativos
|
| 123 |
+
(g_e[t]>0 ESTRITAMENTE para todo t e todo e ativo), os conjuntos de pares
|
| 124 |
+
computados COINCIDEM (Σ_e n_e = A·BT — leitura honesta do modelo de custo,
|
| 125 |
+
Teo 31.5-nota). (iii) FFN: `_ffn_tiles` computa
|
| 126 |
+
`FFN(x) = Σ_c (silu(x·wg_cᵀ) ⊙ (x·wu_cᵀ))·wd_cᵀ` — particionamento do
|
| 127 |
+
hidden: `h = silu(x·wgᵀ) ⊙ (x·wuᵀ)` exatamente, e a saída
|
| 128 |
+
`h·wdᵀ = Σ_c h_c·wd_cᵀ` (linearidade da partição colunar de wd). A única
|
| 129 |
+
diferença é a ordem de acumulação (soma por blocos) — erro ≤ N_bloco·ε·‖h‖
|
| 130 |
+
em fp32 (ε≈1.2e-7), medido: Δy=3e-8. (iv) Agregação: `index_add_` é a
|
| 131 |
+
mesma soma ponderada do `torch.sum(w.T·YE)` (comutatividade — Teo 19.11
|
| 132 |
+
reusado); a normalização `soma_at` é idêntica. (v) Gradientes: autograd
|
| 133 |
+
diferencia index_select/index_add_/linear exatamente; os termos que o
|
| 134 |
+
vetorial calculava com peso zero tinham gradiente NULO e são omitidos —
|
| 135 |
+
∂L/∂θ_e = Σ_t g_e[t]·δ_t·(∂FFN_e/∂θ) coincide. L_lb é função só de g
|
| 136 |
+
(idêntica, Δ=0); L_ort e _Z_ultimo são reconstruídos com os MESMOS
|
| 137 |
+
estimadores (médias por expert sobre as linhas ativas). ∎
|
| 138 |
+
|
| 139 |
+
**Verificação empírica** (suíte v19 + treino real):
|
| 140 |
+
Δy=3.0e-08, Δgrad_x=3.4e-08, Δgrad_w=2.1e-07, Δg=0, ΔL_lb=0, ΔL_ort=7.9e-08;
|
| 141 |
+
em produção (braços B/C): Δy ≤ 7.1e-08.
|
| 142 |
+
|
| 143 |
+
### Teo 31.5 (roofline HONESTO do ganho)
|
| 144 |
+
|
| 145 |
+
**Afirmação.** Neste código o ganho do caminho sônico NÃO é redução de
|
| 146 |
+
linhas de GEMM (o gate foco é softmax sobre todos os ativos ⟹ fator de
|
| 147 |
+
trabalho = 1). O ganho é de IO de PESOS e localidade:
|
| 148 |
+
|
| 149 |
+
1. **Pilha evitada**: o caminho vetorial materializa
|
| 150 |
+
`torch.stack(w_gate/w_up/w_down)` de A experts — cópia de
|
| 151 |
+
S = A·3·d_ff·d·4 bytes por forward (escrita + leitura); o sônico lê os
|
| 152 |
+
pesos de cada expert UMA vez, sem cópia.
|
| 153 |
+
2. **YE em bloco evitado**: o vetorial materializa YE (A, BT, d) de uma
|
| 154 |
+
vez; o sônico materializa por grupo (mesma soma de elementos, pico de
|
| 155 |
+
memória menor).
|
| 156 |
+
3. **Tiles**: com tile_m < d_ff, o bloco de linhas do peso fica na cache
|
| 157 |
+
e é reusado pelas linhas do grupo (localidade — efeito medido por
|
| 158 |
+
wall-clock, não afirmado analiticamente).
|
| 159 |
+
|
| 160 |
+
**Prova.** Contagem de bytes: vetorial escreve a pilha (S bytes) e a lê na
|
| 161 |
+
GEMM (S); sônico lê os pesos originais (S). YE: A·BT·d·4 em ambos (somatório
|
| 162 |
+
dos grupos = YE). Logo a economia modelada é ≥ S por forward (escrita da
|
| 163 |
+
pilha) — ganho_io = (2S + 2·YE)/(S + YE_grupo) ≈ 2 quando S ≳ YE. **Honesti-
|
| 164 |
+
dade:** o wall-clock CPU medido no A/B (2 camadas, d=192, d_ff=160, BT=128)
|
| 165 |
+
foi 0.83–0.875× (sônico ~14% MAIS LENTO) — em GEMMs pequenos o overhead do
|
| 166 |
+
laço Python/index_select domina a economia de cópia; a arquitetura sônica é
|
| 167 |
+
projetada para GEMMs maiores (GPU/NPU — onde a pilha evitada e a localidade
|
| 168 |
+
dominam). NADA afirmamos além do medido. ∎
|
| 169 |
+
|
| 170 |
+
**Nota (31.5-nota).** Se um futuro gate tornar-se esparso POR TOKEN
|
| 171 |
+
(top-k com zeros), o mesmo código passa a omitir linhas automaticamente
|
| 172 |
+
(`linhas = (cols>0)`) — o fator_trabalho deixaria de ser 1 sem mudança
|
| 173 |
+
alguma aqui; a telemetria já mede isso.
|
| 174 |
+
|
| 175 |
+
## 6. Teo 31.6 (guarda de ordem EMA por variante)
|
| 176 |
+
|
| 177 |
+
**Afirmação.** Cada `NucleoJepa` acoplado mantém `_passos_sem_ema`; se mais
|
| 178 |
+
de `tolerancia_ema + 1` chamadas de `perda()` ocorrerem sem
|
| 179 |
+
`passo_pos_backward()`, um `RuntimeError` é lançado. No conselho,
|
| 180 |
+
`tolerancia_ema = lote_efetivo` e `passo_pos_backward()` é chamado APÓS
|
| 181 |
+
`optimizer.step()` no micro-final (mesmo contrato do MotorJepa).
|
| 182 |
+
|
| 183 |
+
**Prova.** O viés do alvo por passo atrasado é O((1−m)·η·G) (Teo 30.1);
|
| 184 |
+
exceder a tolerância significa que o alvo está atrasado ≥ lote_efetivo+1
|
| 185 |
+
passos — a violação vira erro IMEDIATO em vez de viés silencioso. A ordem
|
| 186 |
+
pós-step garante que a EMA absorve o θ APÓS o passo (alvo quase-estacioná-
|
| 187 |
+
rio; Teo 5.2/30.1). No treino real 96 passos: o contador nunca violou
|
| 188 |
+
(0 RuntimeErrors) e o alvo seguiu (drift EMA 6.7e-4 ≈ drift do encoder
|
| 189 |
+
8.3e-4 × (1−m)·passos). ∎
|
| 190 |
+
|
| 191 |
+
## 7. Teo 31.7 (A/B pareado exato)
|
| 192 |
+
|
| 193 |
+
**Afirmação.** Com (i) re-seed APÓS a construção de modelo+treinador nos
|
| 194 |
+
dois braços, (ii) perda JEPA usando gerador DEDICADO (`self._ger`) para as
|
| 195 |
+
máscaras, (iii) conselho em modo `eval()` (sem dropout) e (iv) nenhuma
|
| 196 |
+
outra operação de RNG no caminho JEPA, os dois braços consomem a MESMA
|
| 197 |
+
sequência do RNG global ⟹ mesmos lotes na mesma ordem ⟹ ΔCE_t = CE_B(t) −
|
| 198 |
+
CE_A(t) é pareado por passo.
|
| 199 |
+
|
| 200 |
+
**Prova.** O estado do RNG global determina a escolha de tarefa e o
|
| 201 |
+
montar-lote (mesmos códigos); as únicas operações de RNG novas no braço B
|
| 202 |
+
seriam as máscaras da JEPA e o dropout dos backbones — (ii) desvia as
|
| 203 |
+
máscaras para `torch.Generator` dedicado (semeado por variante) e (iii)
|
| 204 |
+
desliga dropout; logo o fluxo global de B é idêntico ao de A por indução
|
| 205 |
+
sobre os passos. (A construção do conselho consome RNG de init, mas ANTES
|
| 206 |
+
do re-seed pós-construção.) Com lotes idênticos, as diferenças de CE são
|
| 207 |
+
atribuíveis ÀS INTERVENÇÕES (λ, SonicMoE), não ao ruído entre braços —
|
| 208 |
+
variância do estimador pareada ≤ variância de dois-runas independentes
|
| 209 |
+
(Var[X−Y] = Var[X]+Var[Y]−2Cov[X,Y] e Cov>0 sob dados comuns). ∎
|
| 210 |
+
|
| 211 |
+
**Corolário (honestidade do λ).** Sob conflito CE×JEPA, o ΔCE pareado é
|
| 212 |
+
monótono em λ (Teo 28.7-ii reusada: a razão auxiliar/CE governa o desvio).
|
| 213 |
+
Medido (96 passos, corpus real 1015): ΔCE(B, λ=0.3) = +0.0900;
|
| 214 |
+
ΔCE(C, λ=0.1) = +0.0464 — monótono decrescente em λ, como prescrito; a
|
| 215 |
+
perda JEPA cai 74% em ambos (0.808→0.205; 0.847→0.215) — as representações
|
| 216 |
+
APRENDEM nos dois regimes.
|
| 217 |
+
|
| 218 |
+
## 8. Regras operacionais
|
| 219 |
+
|
| 220 |
+
- **R19.1 (opt-in).** `treino.jepa_acoplado.lambda > 0` liga o conselho;
|
| 221 |
+
`modelo.sonicmoe.ativo` liga o caminho sônico. Padrões em
|
| 222 |
+
`configs/base.json` DESLIGADOS (nascimento neutro, Teo 31.1).
|
| 223 |
+
- **R19.2 (parâmetros novos apenas).** NUNCA registrar parâmetros do
|
| 224 |
+
encoder em dois grupos do otimizador (Teo 31.2-cor) — o filtro por
|
| 225 |
+
identidade é obrigatório.
|
| 226 |
+
- **R19.3 (ordem EMA).** `conselho.passo_pos_backward()` APÓS
|
| 227 |
+
`optimizer.step()` no micro-final; tolerância = lote_efetivo (Teo 31.6).
|
| 228 |
+
- **R19.4 (λ calibrado).** Começar com λ=0.1 (razão auxiliar/CE ≤ 0.3 —
|
| 229 |
+
Teo 28.7-ii); medir ΔCE pareado antes de subir λ (Cor. 31.7).
|
| 230 |
+
- **R19.5 (pareamento).** Para A/B pareado: re-seed pós-construção,
|
| 231 |
+
gerador dedicado nas máscaras, conselho em eval (Teo 31.7) — qualquer
|
| 232 |
+
novo consumidor de RNG no caminho JEPA quebra o pareamento.
|
| 233 |
+
- **R19.6 (leitura honesta do SonicMoE).** O ganho garantido é de IO de
|
| 234 |
+
pesos (pilha evitada ≥ S/forward, Teo 31.5); wall-clock CPU em escala
|
| 235 |
+
pequena pode ser neutro/negativo (medido 0.83–0.875×) — reportar ambos.
|
| 236 |
+
- **R19.7 (fase densa).** O caminho sônico só atua na fase FOCO; na densa
|
| 237 |
+
o vetorial mantém a máxima conexão (sem trabalho esparsível — Teo 31.5).
|
| 238 |
+
- **R19.8 (variantes presentes).** O conselho omite variantes sem encoder
|
| 239 |
+
no modelo (nunca erro); o gating renormaliza sobre as ativas (Teo 31.3).
|
| 240 |
+
|
| 241 |
+
## 9. Evidência empírica consolidada (96 passos, corpus real 1015 registros)
|
| 242 |
+
|
| 243 |
+
| Braço | CE início→fim | melhoria | ΔCE pareado vs A | JEPA acoplada |
|
| 244 |
+
|-------|---------------|----------|------------------|----------------|
|
| 245 |
+
| A baseline_v18 | 9.738 → 6.064 | 1.606× | — | — |
|
| 246 |
+
| B v19 λ=0.3 + SonicMoE | 9.726 → 6.296 | 1.545× | +0.0900 | 0.808 → 0.205 (−74%) |
|
| 247 |
+
| C v19 λ=0.1 + SonicMoE | 9.732 → 6.187 | 1.573× | +0.0464 | 0.847 → 0.215 (−75%) |
|
| 248 |
+
|
| 249 |
+
- Drift dos encoders de produção (gradiente JEPA+CE vs CE só):
|
| 250 |
+
imagem 8.26e-4 (B) / 8.75e-4 (C) vs 7.04e-4 (A); áudio 7.95e-4 / 7.97e-4
|
| 251 |
+
vs 6.59e-4 — o gradiente da auto-supervisão chega ao encoder REAL.
|
| 252 |
+
- SonicMoE: equivalência Δy ≤ 7.1e-08 em produção; ganho IO modelado 2.0×
|
| 253 |
+
(pilha evitada 2.21 MB/forward); wallclock CPU 0.83–0.875× (honesto —
|
| 254 |
+
R19.6).
|
| 255 |
+
- Dados reais: coleta FRESCA de 395 registros (LeRobot fetchman, GSM8K,
|
| 256 |
+
MATH-500, hendrycks, OpenR1, IFM socratic) mesclada ao corpus v16.
|
| 257 |
+
- Protocolo: 96 passos × 3 braços com retomada por checkpoint (RAM 4 GiB);
|
| 258 |
+
pareamento por re-seed pós-construção + RNG dedicado (Teo 31.7).
|
| 259 |
+
|
| 260 |
+
## 10. O que NÃO afirmamos
|
| 261 |
+
|
| 262 |
+
- Que o wall-clock CPU do SonicMoE melhore nesta escala (não melhorou:
|
| 263 |
+
0.83–0.875×; o ganho garantido é de IO de pesos).
|
| 264 |
+
- Que a CE de 96 passos BENEFICIE do acoplamento (não beneficiou ainda:
|
| 265 |
+
ΔCE ≤ +0.09 com λ≤0.3; benefícios de representação tipicamente aparecem
|
| 266 |
+
em corridas longas/probes downstream — requer corrida de 9 h).
|
| 267 |
+
- Que o gate foco atual produza economia de linhas (fator_trabalho=1 —
|
| 268 |
+
softmax sobre ativos; a economia por esparsidade exigiria top-k por
|
| 269 |
+
token, mudança de semântica fora do escopo desta rodada).
|
graficos/v19/v19_evidencias.png
ADDED
|
Git LFS Details
|
patches/jepa.py
CHANGED
|
@@ -243,6 +243,16 @@ class MotorJepa(nn.Module):
|
|
| 243 |
# correção D: janelas COM TETO (RAM limitada em treino de 9 h)
|
| 244 |
self.historico = deque(maxlen=self.cfg.janela_telemetria)
|
| 245 |
self._taxa_sucesso = deque(maxlen=self.cfg.janela_telemetria)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
|
| 247 |
# -------- ganho de experiência: tabela de μ por episteme do EMA -------
|
| 248 |
def tabela_mu(self, taxa_sucesso_ema: float) -> float:
|
|
@@ -265,7 +275,19 @@ class MotorJepa(nn.Module):
|
|
| 265 |
return sum(self._taxa_sucesso) / len(self._taxa_sucesso)
|
| 266 |
|
| 267 |
def perda(self, h: torch.Tensor, gerador=None) -> tuple[torch.Tensor, dict]:
|
| 268 |
-
"""h: (B,T,d) saída do tronco. Devolve (perda, telemetria).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
cfg = self.cfg
|
| 270 |
B, T, _ = h.shape
|
| 271 |
if T < 2:
|
|
@@ -334,6 +356,10 @@ class MotorJepa(nn.Module):
|
|
| 334 |
def passo_pos_backward(self) -> float:
|
| 335 |
"""Atualiza o alvo com μ da tabela REAL. Chamar DEPOIS do
|
| 336 |
optimizer.step() — se antes, o alvo enxerga um encoder já atualizado
|
| 337 |
-
e o EMA fica enviesado (ordem obrigatória, relatório §8.3).
|
|
|
|
| 338 |
self.funil_bar.mu = self.tabela_mu(self.taxa_sucesso_media())
|
| 339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
# correção D: janelas COM TETO (RAM limitada em treino de 9 h)
|
| 244 |
self.historico = deque(maxlen=self.cfg.janela_telemetria)
|
| 245 |
self._taxa_sucesso = deque(maxlen=self.cfg.janela_telemetria)
|
| 246 |
+
# v18 — GUARDA DE ORDEM EMA (requisito §1.1.1/§3.1, Teo 30.1):
|
| 247 |
+
# a atualização do alvo DEPENDE de passo_pos_backward() rodar APÓS
|
| 248 |
+
# optimizer.step(); qualquer refatoração do laço pode quebrar essa
|
| 249 |
+
# ordem SEM AVISO. O contador _passos_sem_ema conta perdas() desde
|
| 250 |
+
# a última atualização do alvo; exceder a tolerância (n de
|
| 251 |
+
# micro-passos por grupo do otimizador) LANÇA RuntimeError — a
|
| 252 |
+
# quebra vira erro IMEDIATO e rastreável, não viés silencioso.
|
| 253 |
+
self._passos_sem_ema = 0
|
| 254 |
+
self.tolerancia_micro = 1 # treinador ajusta = lote_efetivo
|
| 255 |
+
self._n_ema_atualizados = 0
|
| 256 |
|
| 257 |
# -------- ganho de experiência: tabela de μ por episteme do EMA -------
|
| 258 |
def tabela_mu(self, taxa_sucesso_ema: float) -> float:
|
|
|
|
| 275 |
return sum(self._taxa_sucesso) / len(self._taxa_sucesso)
|
| 276 |
|
| 277 |
def perda(self, h: torch.Tensor, gerador=None) -> tuple[torch.Tensor, dict]:
|
| 278 |
+
"""h: (B,T,d) saída do tronco. Devolve (perda, telemetria).
|
| 279 |
+
|
| 280 |
+
v18 — guarda de ordem EMA (Teo 30.1): se mais de `tolerancia_micro`
|
| 281 |
+
chamadas ocorrerem sem `passo_pos_backward()` entre elas, o alvo
|
| 282 |
+
está enviesado e o erro é LANÇADO (requisito §1.1.1/§3.1).
|
| 283 |
+
"""
|
| 284 |
+
self._passos_sem_ema += 1
|
| 285 |
+
if self._passos_sem_ema > max(1, int(self.tolerancia_micro)) + 1:
|
| 286 |
+
raise RuntimeError(
|
| 287 |
+
"EMA do JEPA fora de ordem: passo_pos_backward() não foi "
|
| 288 |
+
f"chamado após optimizer.step() há {self._passos_sem_ema - 1} "
|
| 289 |
+
"passos — alvo enviesado (doc 30, Teo 30.1; requisito "
|
| 290 |
+
"§1.1.1/§3.1). Corrija a ordem no laço de treino.")
|
| 291 |
cfg = self.cfg
|
| 292 |
B, T, _ = h.shape
|
| 293 |
if T < 2:
|
|
|
|
| 356 |
def passo_pos_backward(self) -> float:
|
| 357 |
"""Atualiza o alvo com μ da tabela REAL. Chamar DEPOIS do
|
| 358 |
optimizer.step() — se antes, o alvo enxerga um encoder já atualizado
|
| 359 |
+
e o EMA fica enviesado (ordem obrigatória, relatório §8.3).
|
| 360 |
+
v18: reinicia o contador da guarda de ordem (Teo 30.1)."""
|
| 361 |
self.funil_bar.mu = self.tabela_mu(self.taxa_sucesso_media())
|
| 362 |
+
drift = self.funil_bar.atualizar(self.funil)
|
| 363 |
+
self._passos_sem_ema = 0
|
| 364 |
+
self._n_ema_atualizados += 1
|
| 365 |
+
return drift
|
scripts/24_teste_v19.py
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""24_teste_v19.py — TESTE REAL v19 (KHMAMBAJEPA, doc 31 §7).
|
| 3 |
+
|
| 4 |
+
Requisitos do usuário atendidos aqui:
|
| 5 |
+
• "acoplar as variantes JEPA aos encoders de produção com A/B pareado" —
|
| 6 |
+
dois braços com MESMA semente e MESMOS lotes (pareamento exato: a perda
|
| 7 |
+
JEPA usa gerador dedicado + modo eval — não consome o RNG do treino);
|
| 8 |
+
• "ativar SonicMoE no tronco (opt-in)" — braço B com sonicmoe.ativo=true;
|
| 9 |
+
micro-benchmark foco sônico×vetorial (wall-clock + equivalência + IO);
|
| 10 |
+
• "rodar testes de datasets reais" — corpus REAL (LeRobot frames, GSM8K/
|
| 11 |
+
matemática, wikitext) + coleta FRESCA opcional com orçamento.
|
| 12 |
+
|
| 13 |
+
Braços (UMA variante por processo — cada modelo completo):
|
| 14 |
+
A) baseline_v18 — jepa_acoplado λ=0, sonicmoe off (v18 EXATO)
|
| 15 |
+
B) v19_completo — jepa_acoplado λ=0.3 (imagem+audio) + sonicmoe ON
|
| 16 |
+
C) v19_lambda_baixo — idêntico a B com λ=0.1 (prescrição Teo 28.7-ii:
|
| 17 |
+
razão auxiliar/CE ≤ 0.3 — o λ alto desvia capacidade da CE)
|
| 18 |
+
|
| 19 |
+
Uso:
|
| 20 |
+
python3 scripts/24_teste_v19.py --coletar # 1× (dados frescos)
|
| 21 |
+
python3 scripts/24_teste_v19.py --variante baseline_v18 --passos 140
|
| 22 |
+
python3 scripts/24_teste_v19.py --variante v19_completo --passos 140
|
| 23 |
+
python3 scripts/24_teste_v19.py --variante v19_lambda_baixo --passos 96
|
| 24 |
+
python3 scripts/24_teste_v19.py --relatorio # consolida o par
|
| 25 |
+
"""
|
| 26 |
+
from __future__ import annotations
|
| 27 |
+
|
| 28 |
+
import argparse
|
| 29 |
+
import base64
|
| 30 |
+
import importlib.util
|
| 31 |
+
import json
|
| 32 |
+
import os
|
| 33 |
+
import sys
|
| 34 |
+
import time
|
| 35 |
+
|
| 36 |
+
RAIZ = os.environ.get("KHTST_RAIZ", "/home/z/my-project/workspace/repo_src")
|
| 37 |
+
sys.path.insert(0, os.path.join(RAIZ, "src"))
|
| 38 |
+
|
| 39 |
+
CORPUS = os.path.join(RAIZ, "cache_dados", "corpus_v16.jsonl")
|
| 40 |
+
TOKENIZADOR = os.path.join(RAIZ, "cache_dados", "tokenizador_v16.json")
|
| 41 |
+
TOK_ANTIGO = os.path.join(RAIZ, "cache_dados", "tokenizador.json")
|
| 42 |
+
TELEMETRIA = os.path.join(RAIZ, "telemetria_out")
|
| 43 |
+
V19_SCRIPT = os.path.join(RAIZ, "scripts", "19_teste_v16.py")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def carregar_registros(caminho: str) -> list[dict]:
|
| 47 |
+
registros = []
|
| 48 |
+
if not os.path.exists(caminho):
|
| 49 |
+
return registros
|
| 50 |
+
with open(caminho, encoding="utf-8") as f:
|
| 51 |
+
for linha in f:
|
| 52 |
+
try:
|
| 53 |
+
r = json.loads(linha)
|
| 54 |
+
except Exception:
|
| 55 |
+
continue
|
| 56 |
+
if isinstance(r.get("imagem"), str):
|
| 57 |
+
try:
|
| 58 |
+
r["imagem"] = base64.b64decode(r["imagem"])
|
| 59 |
+
except Exception:
|
| 60 |
+
r["imagem"] = None
|
| 61 |
+
if isinstance(r.get("audio"), str):
|
| 62 |
+
try:
|
| 63 |
+
r["audio"] = base64.b64decode(r["audio"])
|
| 64 |
+
except Exception:
|
| 65 |
+
r["audio"] = None
|
| 66 |
+
registros.append(r)
|
| 67 |
+
return registros
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def coletar_fresco() -> dict:
|
| 71 |
+
"""Coleta FRESCA com orçamento (datasets reais; reusa o coletor v16)."""
|
| 72 |
+
spec = importlib.util.spec_from_file_location("teste_v16", V19_SCRIPT)
|
| 73 |
+
mod = importlib.util.module_from_spec(spec)
|
| 74 |
+
spec.loader.exec_module(mod)
|
| 75 |
+
return mod.coletar(max_por_fonte=8, orcamento_s=50.0)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _estado_rng() -> dict:
|
| 79 |
+
import random
|
| 80 |
+
import numpy as np
|
| 81 |
+
import torch
|
| 82 |
+
return {"torch": torch.get_rng_state(),
|
| 83 |
+
"random": random.getstate(),
|
| 84 |
+
"numpy": np.random.get_state()}
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def _restaurar_rng(est: dict) -> None:
|
| 88 |
+
import random
|
| 89 |
+
import numpy as np
|
| 90 |
+
import torch
|
| 91 |
+
torch.set_rng_state(est["torch"])
|
| 92 |
+
random.setstate(est["random"])
|
| 93 |
+
np.random.set_state(est["numpy"])
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def rodar_variante(nome: str, v19: bool, passos: int, parte: int = 0,
|
| 97 |
+
lambda_jac: float = 0.3) -> dict:
|
| 98 |
+
import gc
|
| 99 |
+
import random
|
| 100 |
+
import numpy as np
|
| 101 |
+
import torch
|
| 102 |
+
# CPU sandbox: limitar intraop threads (sem isto o env ignora OMP_NUM_
|
| 103 |
+
# THREADS herdado e o passo fica 2× mais lento por thrashing)
|
| 104 |
+
if not (torch.cuda.is_available() or torch.xpu.is_available()):
|
| 105 |
+
torch.set_num_threads(int(os.environ.get("OMP_NUM_THREADS", "2")))
|
| 106 |
+
from khtst.config import Config
|
| 107 |
+
from khtst.dados.tokenizador import TokenizadorKHTST
|
| 108 |
+
from khtst.memoria.orquestrador import OrquestradorSOM
|
| 109 |
+
from khtst.nucleo.modelo import KHTSTModel
|
| 110 |
+
from khtst.telemetria.hub import TelemetryHub
|
| 111 |
+
from khtst.treino.treinador import TreinadorExtensao
|
| 112 |
+
|
| 113 |
+
SEED = 2026
|
| 114 |
+
cfg = Config()
|
| 115 |
+
cfg.dados.semente = SEED
|
| 116 |
+
cfg.treino.warmup = 8
|
| 117 |
+
cfg.treino.lote = 4
|
| 118 |
+
cfg.treino.lote_efetivo = 1 # protocolo v16 exato (RAM 4 GiB)
|
| 119 |
+
cfg.treino.qat_ultimos_passos = 0
|
| 120 |
+
cfg.treino.ensemble["a_cada"] = 24 # RF menos frequente (RAM)
|
| 121 |
+
cfg.modelo.escalacao["ativo"] = False
|
| 122 |
+
if v19:
|
| 123 |
+
# REQUISITO v19: JEPA ACOPLADA aos encoders de produção + SonicMoE
|
| 124 |
+
cfg.treino.jepa_acoplado["lambda"] = lambda_jac
|
| 125 |
+
cfg.treino.jepa_acoplado["variantes"] = ["imagem", "audio"]
|
| 126 |
+
cfg.treino.jepa_acoplado["pred_hidden"] = 256
|
| 127 |
+
cfg.modelo.sonicmoe["ativo"] = True
|
| 128 |
+
cfg.modelo.sonicmoe["tile_m"] = 32
|
| 129 |
+
else:
|
| 130 |
+
cfg.treino.jepa_acoplado["lambda"] = 0.0
|
| 131 |
+
cfg.modelo.sonicmoe["ativo"] = False
|
| 132 |
+
|
| 133 |
+
os.makedirs(TELEMETRIA, exist_ok=True)
|
| 134 |
+
hub = TelemetryHub(os.path.join(TELEMETRIA, f"treino_v19_{nome}.jsonl"))
|
| 135 |
+
tk = TokenizadorKHTST(TOKENIZADOR if os.path.exists(TOKENIZADOR)
|
| 136 |
+
else TOK_ANTIGO)
|
| 137 |
+
registros = carregar_registros(CORPUS)
|
| 138 |
+
modelo = KHTSTModel(cfg, usar_multimodal=True)
|
| 139 |
+
orquestrador = OrquestradorSOM(cfg.modelo.d_modelo, cfg.som, hub=hub,
|
| 140 |
+
cfg_atencao=None)
|
| 141 |
+
treinar = TreinadorExtensao(cfg, modelo, tk, hub, registros,
|
| 142 |
+
orquestrador_som=orquestrador)
|
| 143 |
+
# PAREAMENTO EXATO (Teo 31.7): re-seed APÓS construir modelo+treinador
|
| 144 |
+
# (a construção consome RNG de init — os dois braços partem do MESMO
|
| 145 |
+
# estado aqui; a perda JEPA não consome o RNG global: gerador dedicado
|
| 146 |
+
# + eval). Braço A não tem conselho — o fluxo de lotes é idêntico.
|
| 147 |
+
_restaurar_rng(est_padrao(SEED))
|
| 148 |
+
|
| 149 |
+
# ---- retomada por PARTES (RAM sandbox: o processo morre entre chamadas;
|
| 150 |
+
# v19 salva TUDO que afeta o pareamento: pesos, otimizador, conselho,
|
| 151 |
+
# contagem de passos, perdas e os 3 estados de RNG)
|
| 152 |
+
ckpt = os.path.join(TELEMETRIA, f"ckpt_v19_{nome}.pt")
|
| 153 |
+
perdas: list[float] = []
|
| 154 |
+
tel_jepa_ac: list[dict] = []
|
| 155 |
+
passo_inicial = 0
|
| 156 |
+
if parte and os.path.exists(ckpt):
|
| 157 |
+
est = torch.load(ckpt, map_location="cpu", weights_only=False)
|
| 158 |
+
modelo.load_state_dict(est["modelo"])
|
| 159 |
+
treinar.otimizador.load_state_dict(est["otimizador"])
|
| 160 |
+
if treinar.conselho_acoplado is not None and est.get("conselho"):
|
| 161 |
+
treinar.conselho_acoplado.load_state_dict(est["conselho"])
|
| 162 |
+
treinar.passo_global = est["passo_global"]
|
| 163 |
+
perdas = est["perdas"]
|
| 164 |
+
tel_jepa_ac = est.get("tel_jepa_ac", [])
|
| 165 |
+
torch.set_rng_state(est["rng_torch"])
|
| 166 |
+
random.setstate(est["rng_py"])
|
| 167 |
+
np.random.set_state(est["rng_np"])
|
| 168 |
+
passo_inicial = est["passo_global"]
|
| 169 |
+
print(f"[retomada] passo_global={est['passo_global']} perdas={len(perdas)}",
|
| 170 |
+
flush=True)
|
| 171 |
+
|
| 172 |
+
# drift dos encoders de produção (medida de treino REAL dos encoders)
|
| 173 |
+
im0 = torch.zeros(0)
|
| 174 |
+
au0 = torch.zeros(0)
|
| 175 |
+
if getattr(modelo, "enc_imagem", None) is not None:
|
| 176 |
+
im0 = modelo.enc_imagem.proj.weight.data.detach().clone()
|
| 177 |
+
if getattr(modelo, "enc_audio", None) is not None:
|
| 178 |
+
au0 = modelo.enc_audio.conv1.weight.data.detach().clone()
|
| 179 |
+
if parte and os.path.exists(ckpt):
|
| 180 |
+
est = torch.load(ckpt, map_location="cpu", weights_only=False)
|
| 181 |
+
im0 = est["im0"]
|
| 182 |
+
au0 = est["au0"]
|
| 183 |
+
|
| 184 |
+
t0 = time.time()
|
| 185 |
+
alvo = min(passos, passo_inicial + parte) if parte else passos
|
| 186 |
+
while treinar.passo_global < alvo:
|
| 187 |
+
tp = time.time()
|
| 188 |
+
perda = treinar._passo()
|
| 189 |
+
if perda == perda:
|
| 190 |
+
perdas.append(perda)
|
| 191 |
+
if parte:
|
| 192 |
+
print(f"[passo {treinar.passo_global} {time.time()-tp:.1f}s "
|
| 193 |
+
f"perda {perda:.3f}]", flush=True)
|
| 194 |
+
if treinar.conselho_acoplado is not None:
|
| 195 |
+
tel = getattr(treinar, "ultima_tel_jac", {})
|
| 196 |
+
if tel:
|
| 197 |
+
tel_jepa_ac.append(
|
| 198 |
+
{k: (float(v) if torch.is_tensor(v) else v)
|
| 199 |
+
for k, v in tel.items()
|
| 200 |
+
if k in ("loss", "ativas", "imagem/loss", "imagem/mse",
|
| 201 |
+
"audio/loss")})
|
| 202 |
+
tempo_s = time.time() - t0
|
| 203 |
+
|
| 204 |
+
if parte:
|
| 205 |
+
torch.save({"modelo": modelo.state_dict(),
|
| 206 |
+
"otimizador": treinar.otimizador.state_dict(),
|
| 207 |
+
"conselho": (treinar.conselho_acoplado.state_dict()
|
| 208 |
+
if treinar.conselho_acoplado is not None else None),
|
| 209 |
+
"passo_global": treinar.passo_global,
|
| 210 |
+
"perdas": perdas, "tel_jepa_ac": tel_jepa_ac,
|
| 211 |
+
"rng_torch": torch.get_rng_state(),
|
| 212 |
+
"rng_py": random.getstate(),
|
| 213 |
+
"rng_np": np.random.get_state(),
|
| 214 |
+
"im0": im0, "au0": au0}, ckpt)
|
| 215 |
+
if treinar.passo_global < passos:
|
| 216 |
+
return {"parcial": True, "passos": treinar.passo_global}
|
| 217 |
+
|
| 218 |
+
res = {
|
| 219 |
+
"variante": nome, "v19": v19, "passos": len(perdas),
|
| 220 |
+
"lote": cfg.treino.lote,
|
| 221 |
+
"perda_media_inicial": round(sum(perdas[:8]) / max(1, len(perdas[:8])), 4),
|
| 222 |
+
"perda_media_final": round(sum(perdas[-8:]) / max(1, len(perdas[-8:])), 4),
|
| 223 |
+
"perda_min": round(min(perdas), 4),
|
| 224 |
+
"perdas_finitas": all(p == p for p in perdas),
|
| 225 |
+
"tempo_s": round(tempo_s, 1),
|
| 226 |
+
"tempo_ms_por_passo": round(1000 * tempo_s / max(1, len(perdas)), 1),
|
| 227 |
+
"n_registros_corpus": len(registros),
|
| 228 |
+
"perdas_por_passo": perdas,
|
| 229 |
+
}
|
| 230 |
+
if getattr(modelo, "enc_imagem", None) is not None and im0.numel():
|
| 231 |
+
res["drift_encoder_imagem"] = float(
|
| 232 |
+
(modelo.enc_imagem.proj.weight.data - im0).abs().max())
|
| 233 |
+
if getattr(modelo, "enc_audio", None) is not None and au0.numel():
|
| 234 |
+
res["drift_encoder_audio"] = float(
|
| 235 |
+
(modelo.enc_audio.conv1.weight.data - au0).abs().max())
|
| 236 |
+
|
| 237 |
+
# ---- SONICMOE micro-benchmark (foco): sônico × vetorial, MESMOS pesos --
|
| 238 |
+
if v19:
|
| 239 |
+
import torch as _t
|
| 240 |
+
moes = [b.moe for b in modelo.blocos
|
| 241 |
+
if getattr(b, "moe", None) is not None]
|
| 242 |
+
if moes:
|
| 243 |
+
modelo.eval()
|
| 244 |
+
modelo.definir_fase_moe("foco")
|
| 245 |
+
# lote sintético do próprio espaço (h do tronco: (B,T,d))
|
| 246 |
+
B, T, d = 2, 32, int(cfg.modelo.d_modelo)
|
| 247 |
+
x = _t.randn(B, T, d)
|
| 248 |
+
# vetorial
|
| 249 |
+
for m in moes:
|
| 250 |
+
m.modo_sonico = False
|
| 251 |
+
_t.manual_seed(SEED)
|
| 252 |
+
_t.cuda.manual_seed_all(SEED)
|
| 253 |
+
t0v = time.time()
|
| 254 |
+
ys = [m(x) for m in moes for _ in range(10)]
|
| 255 |
+
tv = (time.time() - t0v) / 10
|
| 256 |
+
# sônico (mesmos pesos)
|
| 257 |
+
for m in moes:
|
| 258 |
+
m.modo_sonico = True
|
| 259 |
+
_t.manual_seed(SEED)
|
| 260 |
+
_t.cuda.manual_seed_all(SEED)
|
| 261 |
+
t0s = time.time()
|
| 262 |
+
ysn = [m(x) for m in moes for _ in range(10)]
|
| 263 |
+
ts = (time.time() - t0s) / 10
|
| 264 |
+
dy = max(float((a - b).abs().max()) for a, b in zip(ys, ysn))
|
| 265 |
+
io = moes[0].ultimo_io
|
| 266 |
+
res["sonicmoe"] = {
|
| 267 |
+
"n_camadas": len(moes),
|
| 268 |
+
"tempo_vetorial_ms": round(1000 * tv, 2),
|
| 269 |
+
"tempo_sonico_ms": round(1000 * ts, 2),
|
| 270 |
+
"speedup_wallclock": round(tv / max(ts, 1e-9), 3),
|
| 271 |
+
"delta_y_max": dy,
|
| 272 |
+
"ganho_io_modelado": round(io.get("ganho_io", 0.0), 3),
|
| 273 |
+
"pilha_evitada_bytes": io.get("pilha_evitada_bytes", 0),
|
| 274 |
+
"fator_trabalho": round(io.get("fator_trabalho", 0.0), 3),
|
| 275 |
+
"tile_m": io.get("tile_m", 0)}
|
| 276 |
+
# conselho — curva da perda JEPA acoplada (aprendizado real)
|
| 277 |
+
if tel_jepa_ac:
|
| 278 |
+
ini = [t["loss"] for t in tel_jepa_ac[:8]]
|
| 279 |
+
fim = [t["loss"] for t in tel_jepa_ac[-8:]]
|
| 280 |
+
res["jepa_acoplada"] = {
|
| 281 |
+
"passos_com_perda": len(tel_jepa_ac),
|
| 282 |
+
"perda_inicial": round(sum(ini) / max(1, len(ini)), 4),
|
| 283 |
+
"perda_final": round(sum(fim) / max(1, len(fim)), 4),
|
| 284 |
+
"drift_alvo_ema_imagem": float((
|
| 285 |
+
treinar.conselho_acoplado.variantes["imagem"]
|
| 286 |
+
.target.stem.enc.proj.weight.data
|
| 287 |
+
- treinar.conselho_acoplado.variantes["imagem"]
|
| 288 |
+
.stem.enc.proj.weight.data).abs().max())
|
| 289 |
+
if "imagem" in treinar.conselho_acoplado.variantes else None}
|
| 290 |
+
|
| 291 |
+
del treinar, modelo, orquestrador, hub
|
| 292 |
+
gc.collect()
|
| 293 |
+
return res
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
def est_padrao(seed: int) -> dict:
|
| 297 |
+
"""Estado RNG padrão reconstrutível (mesma semente nos dois braços)."""
|
| 298 |
+
import random
|
| 299 |
+
import numpy as np
|
| 300 |
+
import torch
|
| 301 |
+
torch.manual_seed(seed)
|
| 302 |
+
random.seed(seed)
|
| 303 |
+
np.random.seed(seed)
|
| 304 |
+
return {"torch": torch.get_rng_state(), "random": random.getstate(),
|
| 305 |
+
"numpy": np.random.get_state()}
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
def relatorio() -> int:
|
| 309 |
+
braços = ["baseline_v18", "v19_completo", "v19_lambda_baixo"]
|
| 310 |
+
dados = {}
|
| 311 |
+
for nome in braços:
|
| 312 |
+
p = os.path.join(TELEMETRIA, f"v19_{nome}.json")
|
| 313 |
+
if os.path.exists(p):
|
| 314 |
+
with open(p) as f:
|
| 315 |
+
dados[nome] = json.load(f)
|
| 316 |
+
if "baseline_v18" not in dados or "v19_completo" not in dados:
|
| 317 |
+
print("faltam resultados dos braços A/B")
|
| 318 |
+
return 1
|
| 319 |
+
a = dados["baseline_v18"]
|
| 320 |
+
print("=== TESTE REAL v19 — A/B/C PAREADO (doc 31 §7) ===")
|
| 321 |
+
print(f"registros reais: {a['n_registros_corpus']} | passos: {a['passos']}"
|
| 322 |
+
f" | lote: {a['lote']}")
|
| 323 |
+
pa = a["perdas_por_passo"]
|
| 324 |
+
for nome, rotulo in (("baseline_v18", "A baseline_v18 "),
|
| 325 |
+
("v19_completo", "B v19_completo "),
|
| 326 |
+
("v19_lambda_baixo", "C v19_lambda_baixo ")):
|
| 327 |
+
if nome not in dados:
|
| 328 |
+
continue
|
| 329 |
+
r = dados[nome]
|
| 330 |
+
print(f"{rotulo}: {r['perda_media_inicial']} → "
|
| 331 |
+
f"{r['perda_media_final']} ({r.get('melhoria_x', '—')}×)")
|
| 332 |
+
if nome != "baseline_v18":
|
| 333 |
+
pb = r["perdas_por_passo"]
|
| 334 |
+
n = min(len(pa), len(pb))
|
| 335 |
+
deltas = [pb[i] - pa[i] for i in range(n)]
|
| 336 |
+
half = n // 2
|
| 337 |
+
media = sum(deltas) / n
|
| 338 |
+
print(f" ΔCE pareado vs A: {media:+.4f} | 1ª metade "
|
| 339 |
+
f"{sum(deltas[:half]) / max(1, half):+.4f} | 2ª metade "
|
| 340 |
+
f"{sum(deltas[half:]) / max(1, n - half):+.4f}")
|
| 341 |
+
if "jepa_acoplada" in r:
|
| 342 |
+
j = r["jepa_acoplada"]
|
| 343 |
+
print(f" JEPA acoplada: {j['perda_inicial']} → {j['perda_final']}"
|
| 344 |
+
f" | drift alvo EMA imagem {j['drift_alvo_ema_imagem']:.2e}")
|
| 345 |
+
if "sonicmoe" in r:
|
| 346 |
+
s = r["sonicmoe"]
|
| 347 |
+
print(f" SonicMoE: Δy {s['delta_y_max']:.1e} | ganho_io "
|
| 348 |
+
f"{s['ganho_io_modelado']}× | pilha evitada "
|
| 349 |
+
f"{s['pilha_evitada_bytes'] / 1e6:.2f} MB/forward | "
|
| 350 |
+
f"wallclock {s['speedup_wallclock']}× (CPU, escala pequena)")
|
| 351 |
+
for k in ("drift_encoder_imagem", "drift_encoder_audio"):
|
| 352 |
+
linha = " ".join(f"{nome.split('_')[-1]}={dados[nome].get(k, 0):.2e}"
|
| 353 |
+
for nome in dados)
|
| 354 |
+
print(f"{k}: {linha}")
|
| 355 |
+
saida = {"braços": {n: {k: v for k, v in r.items()
|
| 356 |
+
if k != "perdas_por_passo"}
|
| 357 |
+
for n, r in dados.items()}}
|
| 358 |
+
with open(os.path.join(TELEMETRIA, "v19_relatorio_ab.json"), "w",
|
| 359 |
+
encoding="utf-8") as f:
|
| 360 |
+
json.dump(saida, f, ensure_ascii=False, indent=2)
|
| 361 |
+
print("salvo: telemetria_out/v19_relatorio_ab.json")
|
| 362 |
+
return 0
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
def main() -> int:
|
| 366 |
+
ap = argparse.ArgumentParser()
|
| 367 |
+
ap.add_argument("--coletar", action="store_true")
|
| 368 |
+
ap.add_argument("--variante", choices=["baseline_v18", "v19_completo",
|
| 369 |
+
"v19_lambda_baixo"])
|
| 370 |
+
ap.add_argument("--passos", type=int, default=140)
|
| 371 |
+
ap.add_argument("--lambda_jac", type=float, default=0.3)
|
| 372 |
+
ap.add_argument("--parte", type=int, default=0,
|
| 373 |
+
help="passos nesta chamada (retomada por checkpoint)")
|
| 374 |
+
ap.add_argument("--relatorio", action="store_true")
|
| 375 |
+
args = ap.parse_args()
|
| 376 |
+
if args.coletar:
|
| 377 |
+
print(json.dumps(coletar_fresco(), ensure_ascii=False)[:400])
|
| 378 |
+
return 0
|
| 379 |
+
if args.relatorio:
|
| 380 |
+
return relatorio()
|
| 381 |
+
if not args.variante:
|
| 382 |
+
print("--variante obrigatória (ou --coletar / --relatorio)")
|
| 383 |
+
return 1
|
| 384 |
+
v19 = args.variante != "baseline_v18"
|
| 385 |
+
res = rodar_variante(args.variante, v19, args.passos, parte=args.parte,
|
| 386 |
+
lambda_jac=args.lambda_jac)
|
| 387 |
+
if res.get("parcial"):
|
| 388 |
+
print(json.dumps(res))
|
| 389 |
+
return 0
|
| 390 |
+
res["melhoria_x"] = round(res["perda_media_inicial"]
|
| 391 |
+
/ max(res["perda_media_final"], 1e-9), 3)
|
| 392 |
+
caminho = os.path.join(TELEMETRIA, f"v19_{args.variante}.json")
|
| 393 |
+
with open(caminho, "w", encoding="utf-8") as f:
|
| 394 |
+
json.dump(res, f, ensure_ascii=False, indent=2)
|
| 395 |
+
print(json.dumps({k: v for k, v in res.items()
|
| 396 |
+
if k not in ("perdas_por_passo",)}, ensure_ascii=False))
|
| 397 |
+
return 0
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
if __name__ == "__main__":
|
| 401 |
+
raise SystemExit(main())
|
scripts/25_graficos_v19.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""25_graficos_v19.py — evidências v19 (3 painéis):
|
| 3 |
+
1) A/B/C pareado: curvas CE dos 3 braços (corpus real 1015, 96 passos);
|
| 4 |
+
2) JEPA acoplada: perda da auto-supervisão nos encoders de produção;
|
| 5 |
+
3) SonicMoE: equivalência + ganho IO modelado + wallclock honesto.
|
| 6 |
+
Saída: graficos/v19/v19_evidencias.png
|
| 7 |
+
"""
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
import os
|
| 12 |
+
import sys
|
| 13 |
+
|
| 14 |
+
RAIZ = os.environ.get("KHTST_RAIZ", "/home/z/my-project/workspace/repo_src")
|
| 15 |
+
sys.path.insert(0, os.path.join(RAIZ, "src"))
|
| 16 |
+
|
| 17 |
+
import matplotlib
|
| 18 |
+
matplotlib.use("Agg")
|
| 19 |
+
import matplotlib.font_manager as fm
|
| 20 |
+
for fp in ("/usr/share/fonts/truetype/chinese/NotoSansSC-Regular.ttf",
|
| 21 |
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"):
|
| 22 |
+
if os.path.exists(fp):
|
| 23 |
+
fm.fontManager.addfont(fp)
|
| 24 |
+
import matplotlib.pyplot as plt
|
| 25 |
+
|
| 26 |
+
plt.rcParams["font.sans-serif"] = ["DejaVu Sans", "Noto Sans SC"]
|
| 27 |
+
plt.rcParams["axes.unicode_minus"] = False
|
| 28 |
+
|
| 29 |
+
TEL = os.path.join(RAIZ, "telemetria_out")
|
| 30 |
+
SAIDA = os.path.join(RAIZ, "graficos", "v19", "v19_evidencias.png")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def carregar(nome):
|
| 34 |
+
p = os.path.join(TEL, f"v19_{nome}.json")
|
| 35 |
+
if not os.path.exists(p):
|
| 36 |
+
return None
|
| 37 |
+
with open(p, encoding="utf-8") as f:
|
| 38 |
+
return json.load(f)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def suavizar(v, k=9):
|
| 42 |
+
out = []
|
| 43 |
+
for i in range(len(v)):
|
| 44 |
+
j0 = max(0, i - k // 2)
|
| 45 |
+
j1 = min(len(v), i + k // 2 + 1)
|
| 46 |
+
out.append(sum(v[j0:j1]) / (j1 - j0))
|
| 47 |
+
return out
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def main() -> int:
|
| 51 |
+
a = carregar("baseline_v18")
|
| 52 |
+
b = carregar("v19_completo")
|
| 53 |
+
c = carregar("v19_lambda_baixo")
|
| 54 |
+
if not (a and b):
|
| 55 |
+
print("faltam resultados v19 (rode scripts/24_teste_v19.py)")
|
| 56 |
+
return 1
|
| 57 |
+
fig, axes = plt.subplots(1, 3, figsize=(16.5, 4.9),
|
| 58 |
+
constrained_layout=True)
|
| 59 |
+
fig.suptitle("KHTST v19 — JEPA acoplada aos encoders de produção + "
|
| 60 |
+
"SonicMoE no tronco (96 passos, corpus real 1015 registros)",
|
| 61 |
+
fontsize=12, fontweight="bold")
|
| 62 |
+
|
| 63 |
+
# painel 1 — A/B/C pareado
|
| 64 |
+
ax = axes[0]
|
| 65 |
+
ax.plot(suavizar(a["perdas_por_passo"]), label=f"A baseline v18 "
|
| 66 |
+
f"({a['perda_media_inicial']}→{a['perda_media_final']})", lw=1.6)
|
| 67 |
+
ax.plot(suavizar(b["perdas_por_passo"]), label=f"B v19 λ=0.3 "
|
| 68 |
+
f"({b['perda_media_inicial']}→{b['perda_media_final']})", lw=1.6)
|
| 69 |
+
if c:
|
| 70 |
+
ax.plot(suavizar(c["perdas_por_passo"]), label=f"C v19 λ=0.1 "
|
| 71 |
+
f"({c['perda_media_inicial']}→{c['perda_media_final']})", lw=1.6)
|
| 72 |
+
ax.set_xlabel("passo (pareado, mesmos lotes — Teo 31.7)")
|
| 73 |
+
ax.set_ylabel("CE (média móvel 9)")
|
| 74 |
+
ax.set_title("A/B/C pareado — corpus REAL\nΔCE(B)=+0.090, ΔCE(C)=+0.046 "
|
| 75 |
+
"(monótono em λ, Teo 28.7-ii)", fontsize=9.5)
|
| 76 |
+
ax.legend(fontsize=7.5, loc="upper right")
|
| 77 |
+
ax.grid(alpha=0.25)
|
| 78 |
+
|
| 79 |
+
# painel 2 — JEPA acoplada
|
| 80 |
+
ax = axes[1]
|
| 81 |
+
for r, nome in ((b, "B λ=0.3"), (c, "C λ=0.1")):
|
| 82 |
+
if r and "jepa_acoplada" in r:
|
| 83 |
+
j = r["jepa_acoplada"]
|
| 84 |
+
ax.bar([nome], [j["perda_inicial"]], color="#4a7fb5", width=0.5,
|
| 85 |
+
label="início")
|
| 86 |
+
ax.bar([nome], [j["perda_final"]], color="#c2504a", width=0.5,
|
| 87 |
+
label="fim" if nome == "B λ=0.3" else None)
|
| 88 |
+
ax.text(nome, j["perda_final"] + 0.02,
|
| 89 |
+
f"−{(1 - j['perda_final'] / j['perda_inicial']) * 100:.0f}%",
|
| 90 |
+
ha="center", fontsize=9, fontweight="bold")
|
| 91 |
+
ax.set_ylabel("perda JEPA acoplada (MSE+var)")
|
| 92 |
+
ax.set_title("Auto-supervisão nos ENCODERS DE PRODUÇÃO\n(perda −74%: as "
|
| 93 |
+
"representações aprendem; drift encoder > baseline)",
|
| 94 |
+
fontsize=9.5)
|
| 95 |
+
ax.grid(alpha=0.25, axis="y")
|
| 96 |
+
|
| 97 |
+
# painel 3 — SonicMoE
|
| 98 |
+
ax = axes[2]
|
| 99 |
+
if "sonicmoe" in b:
|
| 100 |
+
s = b["sonicmoe"]
|
| 101 |
+
cats = ["Δy (equivalência)", "ganho IO (modelado)", "wallclock CPU"]
|
| 102 |
+
vals = [s["delta_y_max"], s["ganho_io_modelado"], s["speedup_wallclock"]]
|
| 103 |
+
logs = [max(v, 1e-8) for v in vals]
|
| 104 |
+
cores = ["#3d8f5f", "#4a7fb5", "#c2504a"]
|
| 105 |
+
barras = ax.bar(cats, logs, color=cores, width=0.55)
|
| 106 |
+
ax.set_yscale("log")
|
| 107 |
+
ax.axhline(1.0, color="k", lw=0.8, ls="--", alpha=0.6)
|
| 108 |
+
for barra, v, logv in zip(barras, vals, logs):
|
| 109 |
+
txt = f"{v:.1e}" if v < 0.01 else f"{v:.3g}×"
|
| 110 |
+
ax.text(barra.get_x() + barra.get_width() / 2, logv * 1.15, txt,
|
| 111 |
+
ha="center", fontsize=8.5, fontweight="bold")
|
| 112 |
+
ax.set_title("SonicMoE no tronco (fase foco, tile 32)\n"
|
| 113 |
+
"exato (Δy≈6e-8), IO 2.0× modelado, wallclock CPU "
|
| 114 |
+
"0.875× (honesto)", fontsize=9.5)
|
| 115 |
+
ax.grid(alpha=0.25, axis="y")
|
| 116 |
+
ax.set_ylim(1e-8, 30)
|
| 117 |
+
|
| 118 |
+
os.makedirs(os.path.dirname(SAIDA), exist_ok=True)
|
| 119 |
+
fig.savefig(SAIDA, dpi=150)
|
| 120 |
+
print("salvo:", SAIDA)
|
| 121 |
+
return 0
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
if __name__ == "__main__":
|
| 125 |
+
raise SystemExit(main())
|
scripts/26_publicar_v19.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
# -*- coding: utf-8 -*-
|
| 3 |
+
"""Publica o KHTST v19 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).
|
| 7 |
+
|
| 8 |
+
INCLUI: README (card v19) · LICENSE · requirements · configs/ · src/khtst/
|
| 9 |
+
(COM jepa_familia/acoplamento.py NOVO, moe.py com forward_sonico,
|
| 10 |
+
treinador com conselho acoplado, ipc/, kernels .pyx/.c/.so) · scripts/ ·
|
| 11 |
+
docs/matematica/ (provas 00–31) · tests/ (v1–v19) · patches/ ·
|
| 12 |
+
PLANO_KHMAMBAJEPA.md · RELATORIO_KHTST.md · graficos/v19.
|
| 13 |
+
|
| 14 |
+
EXCLUI: venv/ · __pycache__ · cache_dados · telemetria_out ·
|
| 15 |
+
cache_hub_tmp · .git · build · estados*/ · stage*/ · qualquer
|
| 16 |
+
`.pt/.pth/.ckpt/.safetensors` (REQUISITO: nenhum estado de treino no
|
| 17 |
+
Hub) · qualquer arquivo com padrão de token.
|
| 18 |
+
|
| 19 |
+
SEGURANÇA: token EXCLUSIVAMENTE da variável de ambiente HF_TOKEN —
|
| 20 |
+
nunca gravado em arquivo, log, repo ou remote URL.
|
| 21 |
+
|
| 22 |
+
Uso: HF_TOKEN=hf_... python3 scripts/26_publicar_v19.py [--repo ...]
|
| 23 |
+
"""
|
| 24 |
+
import argparse
|
| 25 |
+
import os
|
| 26 |
+
import re
|
| 27 |
+
import shutil
|
| 28 |
+
import sys
|
| 29 |
+
|
| 30 |
+
RAIZ = "/home/z/my-project/khtst"
|
| 31 |
+
STAGE = "/home/z/my-project/khtst/stage_publicar_v19"
|
| 32 |
+
IGNORAR_DIRS = {"__pycache__", "cache_dados", "telemetria_out",
|
| 33 |
+
"cache_hub_tmp", ".git", "build", "venv", ".venv",
|
| 34 |
+
"estados_local", "estados_local_teste", "estados",
|
| 35 |
+
"stage_publicar", "stage_publicar_v17",
|
| 36 |
+
"stage_publicar_v18", "stage_publicar_v19", "hf_stage",
|
| 37 |
+
"download", "upload", "node_modules"}
|
| 38 |
+
IGNORAR_EXT = (".pt", ".pth", ".ckpt", ".safetensors", ".pyc", ".pt.tmp",
|
| 39 |
+
".bin")
|
| 40 |
+
PADRAO_TOKEN = re.compile(r"hf_[A-Za-z0-9]{30,}")
|
| 41 |
+
RESIDUOS_PROIBIDOS = ("bugwatch",)
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def preparar_stage() -> int:
|
| 45 |
+
if os.path.exists(STAGE):
|
| 46 |
+
shutil.rmtree(STAGE)
|
| 47 |
+
n = 0
|
| 48 |
+
for raiz, dirs, arquivos in os.walk(RAIZ):
|
| 49 |
+
dirs[:] = [d for d in dirs if d not in IGNORAR_DIRS]
|
| 50 |
+
rel = os.path.relpath(raiz, RAIZ)
|
| 51 |
+
alvo_raiz = os.path.join(STAGE, rel) if rel != "." else STAGE
|
| 52 |
+
os.makedirs(alvo_raiz, exist_ok=True)
|
| 53 |
+
for a in arquivos:
|
| 54 |
+
if a.endswith(IGNORAR_EXT) or a.startswith("."):
|
| 55 |
+
continue
|
| 56 |
+
if any(r in a.lower() for r in RESIDUOS_PROIBIDOS):
|
| 57 |
+
print(f" [RESÍDUO] ignorado: {a}")
|
| 58 |
+
continue
|
| 59 |
+
origem = os.path.join(raiz, a)
|
| 60 |
+
destino = os.path.join(alvo_raiz, a)
|
| 61 |
+
shutil.copy2(origem, destino)
|
| 62 |
+
n += 1
|
| 63 |
+
return n
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def auditar_stage(n: int) -> list[str]:
|
| 67 |
+
problemas = []
|
| 68 |
+
for raiz, dirs, arquivos in os.walk(STAGE):
|
| 69 |
+
for a in arquivos:
|
| 70 |
+
caminho = os.path.join(raiz, a)
|
| 71 |
+
try:
|
| 72 |
+
if PADRAO_TOKEN.search(open(caminho, "rb").read()
|
| 73 |
+
.decode("utf-8", "ignore") or ""):
|
| 74 |
+
problemas.append(f"TOKEN em {caminho}")
|
| 75 |
+
except Exception:
|
| 76 |
+
pass
|
| 77 |
+
if a.endswith((".pt", ".pth", ".ckpt", ".safetensors", ".bin")):
|
| 78 |
+
problemas.append(f"ESTADO em {caminho}")
|
| 79 |
+
chaves = [
|
| 80 |
+
# v19 — acoplamento + SonicMoE tronco
|
| 81 |
+
"src/khtst/jepa_familia/acoplamento.py",
|
| 82 |
+
"src/khtst/percepcao/moe.py",
|
| 83 |
+
"src/khtst/config.py",
|
| 84 |
+
"configs/base.json",
|
| 85 |
+
"scripts/24_teste_v19.py",
|
| 86 |
+
"scripts/25_graficos_v19.py",
|
| 87 |
+
"graficos/v19/v19_evidencias.png",
|
| 88 |
+
"docs/matematica/31-v19-acoplamento-jepa-sonicmoe.md",
|
| 89 |
+
"tests/test_khtst_v19.py",
|
| 90 |
+
# regressão de conteúdo (v18/v17/v16)
|
| 91 |
+
"src/khtst/jepa_familia/variantes.py",
|
| 92 |
+
"src/khtst/jepa_familia/sonicmoe.py",
|
| 93 |
+
"src/khtst/ipc/khtst_ipc.pyx",
|
| 94 |
+
"src/khtst/ipc/khtst_ipc.cpython-312-x86_64-linux-gnu.so",
|
| 95 |
+
"src/khtst/acelerado/mc_jepa.pyx",
|
| 96 |
+
"src/khtst/treino/treinador.py",
|
| 97 |
+
"docs/matematica/30-v18-robustez-ipc-familia-jepa.md",
|
| 98 |
+
"docs/matematica/29-autorregulacao-hibrida-aprimoramentos.md",
|
| 99 |
+
"patches/jepa.py",
|
| 100 |
+
"PLANO_KHMAMBAJEPA.md", "RELATORIO_KHTST.md", "README.md",
|
| 101 |
+
]
|
| 102 |
+
for c in chaves:
|
| 103 |
+
if not os.path.exists(os.path.join(STAGE, c)):
|
| 104 |
+
problemas.append(f"FALTA chave: {c}")
|
| 105 |
+
if n < 150:
|
| 106 |
+
problemas.append(f"stage pequeno demais: {n} arquivos")
|
| 107 |
+
return problemas
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def publicar(repo_id: str) -> None:
|
| 111 |
+
from huggingface_hub import HfApi
|
| 112 |
+
token = os.environ.get("HF_TOKEN")
|
| 113 |
+
if not token:
|
| 114 |
+
print("ERRO: HF_TOKEN ausente no ambiente")
|
| 115 |
+
sys.exit(1)
|
| 116 |
+
api = HfApi(token=token)
|
| 117 |
+
# UM commit completo e atômico (anti-bloqueio — nada parcial em sequência)
|
| 118 |
+
api.upload_folder(
|
| 119 |
+
folder_path=STAGE,
|
| 120 |
+
repo_id=repo_id,
|
| 121 |
+
repo_type="model",
|
| 122 |
+
commit_message=("KHTST v19 — acoplamento JEPA↔encoders de produção "
|
| 123 |
+
"(vistas de tokens com pesos compartilhados, "
|
| 124 |
+
"gradiente exato no encoder real Teo 31.2, conselho "
|
| 125 |
+
"renormalizado 31.3, alvo EMA pós-step com guarda "
|
| 126 |
+
"31.6) + SonicMoE no tronco opt-in (dispatch "
|
| 127 |
+
"agrupado+tiles, equivalência sônico≡vetorial "
|
| 128 |
+
"Δy≤7.1e-8 Teo 31.4, roofline honesto 31.5, pilha "
|
| 129 |
+
"evitada 2.21MB/forward) + A/B/C PAREADO em dados "
|
| 130 |
+
"reais 1015 registros com pareamento exato por RNG "
|
| 131 |
+
"dedicado (Teo 31.7; ΔCE monótono em λ; JEPA "
|
| 132 |
+
"acoplada −74%) + doc 31 (Teos 31.1–31.7) + suíte "
|
| 133 |
+
"v19 34/34 + regressão v1–v18 zero; sem estados de "
|
| 134 |
+
"treino; sem segredos"),
|
| 135 |
+
delete_patterns=["*"],
|
| 136 |
+
)
|
| 137 |
+
print(f"upload concluído → https://huggingface.co/{repo_id}")
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def auditar_hub(repo_id: str) -> None:
|
| 141 |
+
from huggingface_hub import HfApi
|
| 142 |
+
api = HfApi(token=os.environ.get("HF_TOKEN"))
|
| 143 |
+
arquivos = api.list_repo_files(repo_id, repo_type="model")
|
| 144 |
+
print(f"arquivos no Hub: {len(arquivos)}")
|
| 145 |
+
estados = [a for a in arquivos if a.endswith(
|
| 146 |
+
(".pt", ".pth", ".ckpt", ".safetensors", ".bin"))]
|
| 147 |
+
print("estados de treino no Hub:", len(estados), estados[:5])
|
| 148 |
+
chaves = ["src/khtst/jepa_familia/acoplamento.py",
|
| 149 |
+
"src/khtst/percepcao/moe.py",
|
| 150 |
+
"scripts/24_teste_v19.py",
|
| 151 |
+
"scripts/25_graficos_v19.py",
|
| 152 |
+
"graficos/v19/v19_evidencias.png",
|
| 153 |
+
"docs/matematica/31-v19-acoplamento-jepa-sonicmoe.md",
|
| 154 |
+
"tests/test_khtst_v19.py",
|
| 155 |
+
"src/khtst/jepa_familia/variantes.py",
|
| 156 |
+
"src/khtst/ipc/khtst_ipc.pyx",
|
| 157 |
+
"PLANO_KHMAMBAJEPA.md", "README.md"]
|
| 158 |
+
for c in chaves:
|
| 159 |
+
print((" ✓ " if c in arquivos else " ✗ FALTA ") + c)
|
| 160 |
+
residuos = [a for a in arquivos if "bugwatch" in a.lower()]
|
| 161 |
+
print("resíduos:", len(residuos))
|
| 162 |
+
deprecados = [a for a in arquivos if "stage_publicar" in a
|
| 163 |
+
or "/fase-v" in a]
|
| 164 |
+
print("deprecados/estados antigos:", len(deprecados), deprecados[:5])
|
| 165 |
+
ok = (len(estados) == 0 and not residuos and not deprecados
|
| 166 |
+
and all(c in arquivos for c in chaves))
|
| 167 |
+
print("AUDITORIA DO HUB:", "APROVADA" if ok else "FALHOU")
|
| 168 |
+
return 0 if ok else 1
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
if __name__ == "__main__":
|
| 172 |
+
ap = argparse.ArgumentParser()
|
| 173 |
+
ap.add_argument("--repo", default="PowerMachine/khtst-multimodal-ptbr")
|
| 174 |
+
args = ap.parse_args()
|
| 175 |
+
n = preparar_stage()
|
| 176 |
+
print(f"stage: {n} arquivos")
|
| 177 |
+
problemas = auditar_stage(n)
|
| 178 |
+
if problemas:
|
| 179 |
+
print("AUDITORIA DO STAGE FALHOU:")
|
| 180 |
+
for p in problemas:
|
| 181 |
+
print(" -", p)
|
| 182 |
+
sys.exit(1)
|
| 183 |
+
print("stage OK")
|
| 184 |
+
publicar(args.repo)
|
| 185 |
+
raise SystemExit(auditar_hub(args.repo))
|
src/khtst/acelerado/mc_jepa.c
CHANGED
|
@@ -13,7 +13,7 @@
|
|
| 13 |
"-fopenmp"
|
| 14 |
],
|
| 15 |
"include_dirs": [
|
| 16 |
-
"/home/z/
|
| 17 |
],
|
| 18 |
"language": "c",
|
| 19 |
"name": "mc_jepa",
|
|
|
|
| 13 |
"-fopenmp"
|
| 14 |
],
|
| 15 |
"include_dirs": [
|
| 16 |
+
"/home/z/my-project/workspace/repo_src/venv/lib/python3.12/site-packages/numpy/_core/include"
|
| 17 |
],
|
| 18 |
"language": "c",
|
| 19 |
"name": "mc_jepa",
|
src/khtst/acelerado/mc_jepa.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 1427680
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cef768d9470202185e64412b9dfa755ca4ff425b099da7a3ce2b495bb22436ec
|
| 3 |
size 1427680
|
src/khtst/acelerado/nucleos.c
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/khtst/acelerado/nucleos.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:2ca9940dde730012a43440b385cfe0899d8dc665f816758c1cfe53f02132bbc7
|
| 3 |
+
size 1320256
|
src/khtst/acelerado/rco_gsq.c
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/khtst/acelerado/rco_gsq.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:982937e32ac174c72add65eb409a5fc881254fa99e3f76ec80890f97c6e1e3df
|
| 3 |
+
size 1603056
|
src/khtst/config.py
CHANGED
|
@@ -205,6 +205,10 @@ class ConfigModelo:
|
|
| 205 |
"n_intencoes": len(TAREFAS) + 1})
|
| 206 |
# v5 — atenção ENTRE camadas MoE (doc 16 §§1–2): nasce neutra (α=β=0)
|
| 207 |
atencao_moe: dict = field(default_factory=lambda: {"ativo": True, "alpha0": 0.0})
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
nlg: dict = field(default_factory=lambda: {"ativo": True, "n_estilos": 8,
|
| 209 |
"peso_rerank": 0.35})
|
| 210 |
# encoders multimodais "completo pesado" com escala configurável:
|
|
@@ -287,6 +291,14 @@ class ConfigTreino:
|
|
| 287 |
jepa: dict = field(default_factory=lambda: {
|
| 288 |
"lambda_jepa": 0.0, "d_latente": 64, "tau": 2, "eps_max": 2,
|
| 289 |
"warmup_passo": 500, "janela_telemetria": 200})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
# v2 — barreira analítica de LR (ABMO corrigido, doc 10 §6)
|
| 291 |
abmo: dict = field(default_factory=lambda: {
|
| 292 |
"ativo": True, "margem": 0.25, "cap_min": 0.05, "cap_max": 500.0})
|
|
|
|
| 205 |
"n_intencoes": len(TAREFAS) + 1})
|
| 206 |
# v5 — atenção ENTRE camadas MoE (doc 16 §§1–2): nasce neutra (α=β=0)
|
| 207 |
atencao_moe: dict = field(default_factory=lambda: {"ativo": True, "alpha0": 0.0})
|
| 208 |
+
# v19 (doc 31 §3) — SONICMOE no tronco (OPT-IN): dispatch agrupado por
|
| 209 |
+
# expert + GEMM em tiles + telemetria IO nas camadas MoE (fase foco).
|
| 210 |
+
# Nascimento neutro — ativo=false ⟹ caminho vetorial v8 EXATO (Teo 31.1).
|
| 211 |
+
sonicmoe: dict = field(default_factory=lambda: {"ativo": False, "tile_m": 64})
|
| 212 |
nlg: dict = field(default_factory=lambda: {"ativo": True, "n_estilos": 8,
|
| 213 |
"peso_rerank": 0.35})
|
| 214 |
# encoders multimodais "completo pesado" com escala configurável:
|
|
|
|
| 291 |
jepa: dict = field(default_factory=lambda: {
|
| 292 |
"lambda_jepa": 0.0, "d_latente": 64, "tau": 2, "eps_max": 2,
|
| 293 |
"warmup_passo": 500, "janela_telemetria": 200})
|
| 294 |
+
# v19 (doc 31 §2) — CONSELHO JEPA ACOPLADO AOS ENCODERS DE PRODUÇÃO
|
| 295 |
+
# (requisito): as vistas de tokens COMPARTILHAM os pesos de
|
| 296 |
+
# CodificadorImagem/Audio/Video — o gradiente JEPA treina os encoders
|
| 297 |
+
# reais. Nascimento neutro — lambda=0 ⟹ desligado (v19 ≡ v18).
|
| 298 |
+
jepa_acoplado: dict = field(default_factory=lambda: {
|
| 299 |
+
"lambda": 0.0, "variantes": ["imagem", "audio", "video"],
|
| 300 |
+
"ema_momentum": 0.996, "peso_var": 0.01, "peso_iso": 0.0,
|
| 301 |
+
"pred_hidden": 256, "aprende_gate": False})
|
| 302 |
# v2 — barreira analítica de LR (ABMO corrigido, doc 10 §6)
|
| 303 |
abmo: dict = field(default_factory=lambda: {
|
| 304 |
"ativo": True, "margem": 0.25, "cap_min": 0.05, "cap_max": 500.0})
|
src/khtst/jepa_familia/acoplamento.py
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""Acoplamento das variantes JEPA aos ENCODERS DE PRODUÇÃO (v19 — doc 31 §2;
|
| 3 |
+
requisito: "acoplar as variantes JEPA aos encoders de produção com A/B pareado").
|
| 4 |
+
|
| 5 |
+
PROBLEMA (v18): a família JEPA (jepa_familia/variantes.py) tem STEMS PRÓPRIOS
|
| 6 |
+
(Conv2d pequenos) — a auto-supervisão treinava esses stems paralelos, NÃO os
|
| 7 |
+
encoders que o modelo realmente usa (CodificadorImagem/Audio/Video). A v19
|
| 8 |
+
fecha o laço: a variante acoplada consome uma VISTA DE TOKENS do encoder de
|
| 9 |
+
produção COMPARTILHANDO os pesos — o gradiente JEPA flui PARA o encoder real
|
| 10 |
+
(auto-supervisão do próprio modelo, não de um gêmeo paralelo).
|
| 11 |
+
|
| 12 |
+
ARQUITETURA (Teo 31.2 — gradiente compartilhado):
|
| 13 |
+
online: z = Backbone(vista_θ(x)) — vista usa os MÓDULOS do encoder de
|
| 14 |
+
produção (proj/pos/cls/blocos/bigru etc.) SEM cópia: ∂L_JEPA/∂θ_enc
|
| 15 |
+
é o gradiente exato da cadeia (mesmo grafo do forward multimodal).
|
| 16 |
+
alvo: z̃ = Backbone(vista_θ̃(x)) — θ̃ = EMA(θ): deepcopy do stem no
|
| 17 |
+
nascimento, atualizado por θ̃ ← m·θ̃ + (1−m)·θ APÓS optimizer.step()
|
| 18 |
+
(contrato passo_pos_backward, Teos 30.1/31.6 — guarda de ordem).
|
| 19 |
+
perda: hinge por dimensão + isotropia opt-in (herdado de NucleoJepa).
|
| 20 |
+
|
| 21 |
+
NASCIMENTO NEUTRO (Teo 31.1): com treino.jepa_acoplado ausente/λ=0, NENHUM
|
| 22 |
+
parâmetro novo é criado e o treino é o v18 EXATO.
|
| 23 |
+
|
| 24 |
+
A/B PAREADO (Teo 31.7): mesma semente ⟹ mesmos lotes na MESMA ordem; ΔCE_t
|
| 25 |
+
pareado por passo reduz a variância vs dois-runas independentes (par de
|
| 26 |
+
gêmeos com o mesmo ruído de dados — controle do ruído entre braços).
|
| 27 |
+
"""
|
| 28 |
+
from __future__ import annotations
|
| 29 |
+
|
| 30 |
+
import copy
|
| 31 |
+
|
| 32 |
+
import torch
|
| 33 |
+
import torch.nn as nn
|
| 34 |
+
|
| 35 |
+
from khtst.jepa_familia.base import NucleoJepa
|
| 36 |
+
from khtst.jepa_familia.conselho import ConselhoJepas
|
| 37 |
+
|
| 38 |
+
__all__ = ["VistaTokensImagem", "VistaTokensAudio", "VistaFramesVideo",
|
| 39 |
+
"VarianteAcoplada", "ConselhoAcoplado", "construir_conselho_acoplado"]
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
# ------------------------------------------------------------------ vistas --
|
| 43 |
+
class VistaTokensImagem(nn.Module):
|
| 44 |
+
"""Vista de TOKENS do CodificadorImagem (pesos COMPARTILHADOS): roda
|
| 45 |
+
proj → [CLS] → +pos → blocos → +BiGRU e devolve TODA a sequência
|
| 46 |
+
(B, 1+n_patches, d_enc) — exatamente os features que alimentam o
|
| 47 |
+
pooling [CLS] do forward de produção (a pooled CLS é uma função destes
|
| 48 |
+
tokens; a JEPA prevê a representação, não o pixel)."""
|
| 49 |
+
|
| 50 |
+
def __init__(self, enc: nn.Module):
|
| 51 |
+
super().__init__()
|
| 52 |
+
self.enc = enc
|
| 53 |
+
|
| 54 |
+
def forward(self, img: torch.Tensor) -> torch.Tensor:
|
| 55 |
+
x = self.enc.proj(img)
|
| 56 |
+
x = x.flatten(2).transpose(1, 2)
|
| 57 |
+
cls = self.enc.cls.expand(x.shape[0], -1, -1)
|
| 58 |
+
x = torch.cat([cls, x], dim=1) + self.enc.pos[:, : x.shape[1] + 1]
|
| 59 |
+
for bloco in self.enc.blocos:
|
| 60 |
+
x, _, _ = bloco(x)
|
| 61 |
+
x = x + self.enc.bigru(x)
|
| 62 |
+
return self.enc.norm(x)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class VistaTokensAudio(nn.Module):
|
| 66 |
+
"""Vista de TOKENS do CodificadorAudio: log-mel (buffers do encoder) →
|
| 67 |
+
convs → +BiGRU → blocos → tokens (B, T', d_enc) — os MESMOS features
|
| 68 |
+
que entram no pooling de atenção do forward de produção."""
|
| 69 |
+
|
| 70 |
+
def __init__(self, enc: nn.Module):
|
| 71 |
+
super().__init__()
|
| 72 |
+
self.enc = enc
|
| 73 |
+
|
| 74 |
+
def forward(self, onda: torch.Tensor) -> torch.Tensor:
|
| 75 |
+
from khtst.percepcao.audio import log_mel_spectrogram
|
| 76 |
+
import torch.nn.functional as F
|
| 77 |
+
if not hasattr(self.enc, "mel_filtros"):
|
| 78 |
+
self.enc.register_buffer(
|
| 79 |
+
"mel_filtros",
|
| 80 |
+
_filtros_mel_enc(self.enc.n_mels, 16000, 400), persistent=False)
|
| 81 |
+
spec = log_mel_spectrogram(onda, self.enc.n_mels)
|
| 82 |
+
x = torch.einsum("bft,mf->bmt", spec, self.enc.mel_filtros)
|
| 83 |
+
x = F.gelu(self.enc.conv1(x))
|
| 84 |
+
x = F.gelu(self.enc.conv2(x))
|
| 85 |
+
x = x.transpose(1, 2)
|
| 86 |
+
x = x + self.enc.bigru(x)
|
| 87 |
+
for bloco in self.enc.blocos:
|
| 88 |
+
x, _, _ = bloco(x)
|
| 89 |
+
return self.enc.norm(x)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class VistaFramesVideo(nn.Module):
|
| 93 |
+
"""Vista de TOKENS do CodificadorVideo: CLS de cada frame amostrado
|
| 94 |
+
(B, t, d_enc) — a sequência que alimenta a fusão temporal por atenção.
|
| 95 |
+
Frames são amostrados com o MESMO linspace do encoder de produção."""
|
| 96 |
+
|
| 97 |
+
def __init__(self, enc: nn.Module):
|
| 98 |
+
super().__init__()
|
| 99 |
+
self.enc = enc
|
| 100 |
+
|
| 101 |
+
def forward(self, video: torch.Tensor) -> torch.Tensor:
|
| 102 |
+
B, T = video.shape[0], video.shape[1]
|
| 103 |
+
idx = torch.linspace(0, T - 1, min(self.enc.n_frames, T)).long()
|
| 104 |
+
frames = video[:, idx]
|
| 105 |
+
toks = [self.enc._embed_frame(frames[:, i])
|
| 106 |
+
for i in range(frames.shape[1])]
|
| 107 |
+
return torch.stack(toks, dim=1)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _filtros_mel_enc(n_mels: int, sr: int, n_fft: int) -> torch.Tensor:
|
| 111 |
+
"""Reusa a matriz triangular do módulo de áudio (fonte única)."""
|
| 112 |
+
from khtst.percepcao.audio import _filtros_mel
|
| 113 |
+
return _filtros_mel(n_mels, sr, n_fft)
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
# --------------------------------------------------------------- variante --
|
| 117 |
+
class VarianteAcoplada(NucleoJepa):
|
| 118 |
+
"""NucleoJepa cujo stem É a vista do encoder de produção (pesos vivos).
|
| 119 |
+
|
| 120 |
+
O alvo EMA é deepcopy do stem NO NASCIMENTO: a cópia não recebe
|
| 121 |
+
gradiente e acumula θ̃ = EMA(θ_enc ∪ θ_head). O encoder de produção
|
| 122 |
+
continua a ser usado pelo forward multimodal normal — os dois papéis
|
| 123 |
+
(perda CE via prefixo e perda JEPA via vista) GRAFAM no mesmo θ (Teo 31.2).
|
| 124 |
+
"""
|
| 125 |
+
|
| 126 |
+
def __init__(self, vista: nn.Module, d_enc: int,
|
| 127 |
+
pred_hidden: int = 256, tolerancia_ema: int = 1,
|
| 128 |
+
semente: int = 2026, **kw):
|
| 129 |
+
super().__init__(dim=d_enc, pred_hidden=pred_hidden,
|
| 130 |
+
tolerancia_ema=tolerancia_ema, **kw)
|
| 131 |
+
self.stem = vista
|
| 132 |
+
self._finalizar_encoder()
|
| 133 |
+
# v19 — gerador DEDICADO p/ máscaras (não consome o RNG global do
|
| 134 |
+
# treino — pareamento A/B exato, Teo 31.7)
|
| 135 |
+
self._ger = torch.Generator()
|
| 136 |
+
self._ger.manual_seed(int(semente))
|
| 137 |
+
self.mod = d_enc # telemetria
|
| 138 |
+
|
| 139 |
+
def extra(self) -> dict:
|
| 140 |
+
return {"d_enc": self.mod}
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class ConselhoAcoplado(ConselhoJepas):
|
| 144 |
+
"""ConselhoJepas sobre variantes ACOPLADAS + contrato do treinador:
|
| 145 |
+
passo_pos_backward() APÓS optimizer.step() (Teo 31.6 — mesma ordem do
|
| 146 |
+
MotorJepa: EMA sem viés) e tolerância da guarda de ordem injetada."""
|
| 147 |
+
|
| 148 |
+
def __init__(self, variantes: dict[str, nn.Module],
|
| 149 |
+
tolerancia_ema: int = 1, **kw):
|
| 150 |
+
super().__init__(variantes, **kw)
|
| 151 |
+
self.tolerancia_ema = max(1, int(tolerancia_ema))
|
| 152 |
+
for n in self.nomes:
|
| 153 |
+
v = self.variantes[n]
|
| 154 |
+
if hasattr(v, "tolerancia_ema"):
|
| 155 |
+
v.tolerancia_ema = self.tolerancia_ema
|
| 156 |
+
|
| 157 |
+
def passo_pos_backward(self, ativos: list[str] | None = None):
|
| 158 |
+
"""EMA de cada variante que participou do passo (ordem pós-step)."""
|
| 159 |
+
for n in (ativos if ativos is not None else self.nomes):
|
| 160 |
+
v = self.variantes[n]
|
| 161 |
+
if hasattr(v, "update_target"):
|
| 162 |
+
v.update_target()
|
| 163 |
+
|
| 164 |
+
def telemetria(self) -> dict:
|
| 165 |
+
tel = {"jepa_ac/variantes": float(len(self.nomes))}
|
| 166 |
+
with torch.no_grad():
|
| 167 |
+
w = self.pesos()
|
| 168 |
+
for i, n in enumerate(self.nomes):
|
| 169 |
+
tel[f"jepa_ac/gate_{n}"] = float(w[i])
|
| 170 |
+
return tel
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
# ---------------------------------------------------------------- fábrica --
|
| 174 |
+
def construir_conselho_acoplado(modelo: nn.Module, cfg_jac: dict,
|
| 175 |
+
lote_efetivo: int = 1) -> ConselhoAcoplado:
|
| 176 |
+
"""Constrói o conselho com as variantes das modalidades PRESENTES no
|
| 177 |
+
modelo (enc_imagem/enc_audio/enc_video). Falta de encoder ⟹ variante
|
| 178 |
+
omitida (nunca erro — o lote de treino manda)."""
|
| 179 |
+
nomes = list(cfg_jac.get("variantes", ["imagem", "audio", "video"]))
|
| 180 |
+
comum = dict(ema_momentum=float(cfg_jac.get("ema_momentum", 0.996)),
|
| 181 |
+
peso_var=float(cfg_jac.get("peso_var", 0.01)),
|
| 182 |
+
peso_iso=float(cfg_jac.get("peso_iso", 0.0)))
|
| 183 |
+
pred_hidden = int(cfg_jac.get("pred_hidden", 256))
|
| 184 |
+
variantes: dict[str, nn.Module] = {}
|
| 185 |
+
enc = getattr(modelo, "enc_imagem", None)
|
| 186 |
+
if "imagem" in nomes and enc is not None:
|
| 187 |
+
variantes["imagem"] = VarianteAcoplada(
|
| 188 |
+
VistaTokensImagem(enc), int(enc.proj.out_channels),
|
| 189 |
+
pred_hidden=pred_hidden, **comum)
|
| 190 |
+
enc = getattr(modelo, "enc_audio", None)
|
| 191 |
+
if "audio" in nomes and enc is not None:
|
| 192 |
+
variantes["audio"] = VarianteAcoplada(
|
| 193 |
+
VistaTokensAudio(enc), int(enc.conv2.out_channels),
|
| 194 |
+
pred_hidden=pred_hidden, **comum)
|
| 195 |
+
enc = getattr(modelo, "enc_video", None)
|
| 196 |
+
if "video" in nomes and enc is not None:
|
| 197 |
+
variantes["video"] = VarianteAcoplada(
|
| 198 |
+
VistaFramesVideo(enc), int(enc.saida.in_features),
|
| 199 |
+
pred_hidden=pred_hidden, **comum)
|
| 200 |
+
if not variantes:
|
| 201 |
+
raise RuntimeError(
|
| 202 |
+
"jepa_acoplado: nenhum encoder de produção disponível "
|
| 203 |
+
"(usar_multimodal=False?) — nada a acoplar")
|
| 204 |
+
conselho = ConselhoAcoplado(
|
| 205 |
+
variantes, tolerancia_ema=max(1, int(lote_efetivo)),
|
| 206 |
+
aprende_gate=bool(cfg_jac.get("aprende_gate", False)))
|
| 207 |
+
# v19 — modo eval: SEM dropout na perda JEPA (determinismo do par A/B;
|
| 208 |
+
# gradientes fluem normalmente — eval afeta só dropout/BN)
|
| 209 |
+
conselho.eval()
|
| 210 |
+
return conselho
|
src/khtst/jepa_familia/base.py
CHANGED
|
@@ -131,10 +131,17 @@ class NucleoJepa(nn.Module):
|
|
| 131 |
|
| 132 |
# -------------------------------------------------------- interface --
|
| 133 |
def mascarar(self, tokens: torch.Tensor):
|
| 134 |
-
"""Devolve (idx_ctx, idx_tgt) — política de divisão contexto/alvo.
|
|
|
|
|
|
|
|
|
|
| 135 |
n = tokens.shape[1]
|
| 136 |
meio = n // 2
|
| 137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
return idx.sort().values[:meio], idx.sort().values[meio:]
|
| 139 |
|
| 140 |
# ------------------------------------------------------------ perda --
|
|
|
|
| 131 |
|
| 132 |
# -------------------------------------------------------- interface --
|
| 133 |
def mascarar(self, tokens: torch.Tensor):
|
| 134 |
+
"""Devolve (idx_ctx, idx_tgt) — política de divisão contexto/alvo.
|
| 135 |
+
v19: usa um GERADOR DEDICADO (self._ger) em vez do RNG global — a
|
| 136 |
+
perda JEPA não consome o fluxo de aleatoriedade do treino, preser-
|
| 137 |
+
vando o pareamento A/B (mesmos lotes nos dois braços; Teo 31.7)."""
|
| 138 |
n = tokens.shape[1]
|
| 139 |
meio = n // 2
|
| 140 |
+
gen = getattr(self, "_ger", None)
|
| 141 |
+
if gen is not None:
|
| 142 |
+
idx = torch.randperm(n, generator=gen).to(tokens.device)
|
| 143 |
+
else:
|
| 144 |
+
idx = torch.randperm(n, device=tokens.device)
|
| 145 |
return idx.sort().values[:meio], idx.sort().values[meio:]
|
| 146 |
|
| 147 |
# ------------------------------------------------------------ perda --
|
src/khtst/nucleo/modelo.py
CHANGED
|
@@ -151,6 +151,24 @@ class KHTSTModel(nn.Module):
|
|
| 151 |
alpha0=float(cfg_atn_moe.get("alpha0", 0.0)))
|
| 152 |
# confiança corrente (h_t do AgenteConfiança — definida pelo treino)
|
| 153 |
self.confianca_corrente: float = 0.5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 154 |
# v6 (doc 18 §1): ROTEADOR S-SOM compartilhado pelas camadas MoE —
|
| 155 |
# roteamento por S-SOM com nascimento neutro (Teorema 18.1)
|
| 156 |
cfg_rot = dict(getattr(m, "roteador", {}) or {})
|
|
|
|
| 151 |
alpha0=float(cfg_atn_moe.get("alpha0", 0.0)))
|
| 152 |
# confiança corrente (h_t do AgenteConfiança — definida pelo treino)
|
| 153 |
self.confianca_corrente: float = 0.5
|
| 154 |
+
# v19 (doc 31 §3) — SONICMOE NO TRONCO (OPT-IN, requisito): execução
|
| 155 |
+
# com dispatch agrupado por expert + GEMM em tiles + telemetria IO
|
| 156 |
+
# nas camadas MoE (ativa na fase FOCO; a densa mantém o caminho
|
| 157 |
+
# vetorial — no denso todos os pares (token, expert) são ativos).
|
| 158 |
+
# Nascimento neutro (Teo 31.1): sem a chave "sonicmoe" OU ativo=false
|
| 159 |
+
# ⟹ caminho vetorial v8 EXATO (equivalência provada: Δy ≤ 3e-8,
|
| 160 |
+
# Δgrad ≤ 2e-7, gate bit-idêntico — medido na suíte v19).
|
| 161 |
+
cfg_sonic = dict(getattr(m, "sonicmoe", {}) or {})
|
| 162 |
+
self.sonicmoe_ativo = False
|
| 163 |
+
if self.usar_moe and bool(cfg_sonic.get("ativo", False)):
|
| 164 |
+
tile_m = int(cfg_sonic.get("tile_m", 64))
|
| 165 |
+
n_ligadas = 0
|
| 166 |
+
for bloco in self.blocos:
|
| 167 |
+
moe = getattr(bloco, "moe", None)
|
| 168 |
+
if moe is not None and hasattr(moe, "ativar_sonico"):
|
| 169 |
+
moe.ativar_sonico(tile_m=tile_m)
|
| 170 |
+
n_ligadas += 1
|
| 171 |
+
self.sonicmoe_ativo = n_ligadas > 0
|
| 172 |
# v6 (doc 18 §1): ROTEADOR S-SOM compartilhado pelas camadas MoE —
|
| 173 |
# roteamento por S-SOM com nascimento neutro (Teorema 18.1)
|
| 174 |
cfg_rot = dict(getattr(m, "roteador", {}) or {})
|
src/khtst/percepcao/moe.py
CHANGED
|
@@ -116,6 +116,20 @@ class MoEAgrupavel(nn.Module):
|
|
| 116 |
self.perda_ort: torch.Tensor = torch.zeros(())
|
| 117 |
self.uso_ema: torch.Tensor | None = None # EMA de ativação (N,)
|
| 118 |
self.ultimo_ignorados: int = 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
# ---------------- utilidades ----------------
|
| 121 |
def indice_grupo(self, tarefa: str | None) -> int:
|
|
@@ -136,9 +150,12 @@ class MoEAgrupavel(nn.Module):
|
|
| 136 |
perda = perda + tr / (idx.numel() * (idx.numel() - 1))
|
| 137 |
return perda / max(self.G, 1)
|
| 138 |
|
| 139 |
-
# ----------------
|
| 140 |
-
def
|
| 141 |
-
"""
|
|
|
|
|
|
|
|
|
|
| 142 |
B, T, d = x.shape
|
| 143 |
x_flat = x.reshape(B * T, d)
|
| 144 |
# v3 (doc 11 §12): roteamento POR TOKEN e CAUSAL — cada posição é
|
|
@@ -190,6 +207,19 @@ class MoEAgrupavel(nn.Module):
|
|
| 190 |
mask_ativos = torch.zeros_like(s, dtype=torch.bool)
|
| 191 |
mask_ativos[:, ativos] = True
|
| 192 |
self.ultimo_ignorados = int((~mask_ativos).sum(dim=1).float().mean())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
# v8 (item e): computa APENAS os experts ativos num único passe vetorial
|
| 194 |
# (vmap/bmm — Teorema 19.11); na fase densa N = todos. Equivale ao laço
|
| 195 |
# original (Prop. 10.4 preservada por indexação de pesos, não por laço).
|
|
@@ -257,6 +287,121 @@ class MoEAgrupavel(nn.Module):
|
|
| 257 |
self.perda_ort = self._ort_intra_grupo(A)
|
| 258 |
return y.reshape(B, T, d)
|
| 259 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
# ---------------- métricas ----------------
|
| 261 |
def estatisticas(self) -> dict:
|
| 262 |
out = {"fase": "densa" if self.fase_densa else "foco",
|
|
|
|
| 116 |
self.perda_ort: torch.Tensor = torch.zeros(())
|
| 117 |
self.uso_ema: torch.Tensor | None = None # EMA de ativação (N,)
|
| 118 |
self.ultimo_ignorados: int = 0
|
| 119 |
+
# v19 (doc 31 §3) — SONICMOE no tronco (OPT-IN, nascimento neutro):
|
| 120 |
+
# modo_sonico=False ⟹ caminho vetorial v8 EXATO (Teo 31.1). ativar_
|
| 121 |
+
# sonico() liga dispatch AGRUPADO por expert + GEMM em tiles +
|
| 122 |
+
# telemetria IO (Teo 31.4/31.5) — mesma matemática do gate.
|
| 123 |
+
self.modo_sonico = False
|
| 124 |
+
self.tile_m_sonico = 64
|
| 125 |
+
self.ultimo_io: dict = {}
|
| 126 |
+
|
| 127 |
+
def ativar_sonico(self, tile_m: int = 64):
|
| 128 |
+
"""v19 — liga a execução SonicMoE (opt-in; fase foco). O gate duplo
|
| 129 |
+
(âncoras/viés/roteador) é inalterado — só a ESTRATÉGIA de execução
|
| 130 |
+
dos experts muda (Teo 31.4: equivalência de saída e gradiente)."""
|
| 131 |
+
self.modo_sonico = True
|
| 132 |
+
self.tile_m_sonico = max(8, int(tile_m))
|
| 133 |
|
| 134 |
# ---------------- utilidades ----------------
|
| 135 |
def indice_grupo(self, tarefa: str | None) -> int:
|
|
|
|
| 150 |
perda = perda + tr / (idx.numel() * (idx.numel() - 1))
|
| 151 |
return perda / max(self.G, 1)
|
| 152 |
|
| 153 |
+
# ---------------- v19: gate extraído (fonte única p/ os 2 caminhos) --
|
| 154 |
+
def _computar_gate(self, x: torch.Tensor, tarefa: str | None):
|
| 155 |
+
"""Cálculo do gate duplo EXTRAÍDO do forward v8 VERBATIM (nenhuma
|
| 156 |
+
mudança matemática — Teo 31.1): âncoras + viés de grupo + retro-
|
| 157 |
+
alimentação β + roteador S-SOM + softmax denso OU top-k com margem.
|
| 158 |
+
Devolve (x_flat, g, mask_ativos) com formas (BT,d),(BT,N),(BT,N)."""
|
| 159 |
B, T, d = x.shape
|
| 160 |
x_flat = x.reshape(B * T, d)
|
| 161 |
# v3 (doc 11 §12): roteamento POR TOKEN e CAUSAL — cada posição é
|
|
|
|
| 207 |
mask_ativos = torch.zeros_like(s, dtype=torch.bool)
|
| 208 |
mask_ativos[:, ativos] = True
|
| 209 |
self.ultimo_ignorados = int((~mask_ativos).sum(dim=1).float().mean())
|
| 210 |
+
return x_flat, g, mask_ativos
|
| 211 |
+
|
| 212 |
+
# ---------------- forward ----------------
|
| 213 |
+
def forward(self, x: torch.Tensor, tarefa: str | None = None) -> torch.Tensor:
|
| 214 |
+
"""x: (B, T, d) → saída (B, T, d) + perdas auxiliares atualizadas.
|
| 215 |
+
v19: com modo_sonico (opt-in) e fase FOCO, delega ao caminho SonicMoE
|
| 216 |
+
(dispatch agrupado + tiles — Teo 31.4); fase densa mantém o caminho
|
| 217 |
+
vetorial (no denso todos os tokens vão a todos os experts — não há
|
| 218 |
+
trabalho esparsível: Teo 31.5-nota)."""
|
| 219 |
+
if self.modo_sonico and not self.fase_densa:
|
| 220 |
+
return self.forward_sonico(x, tarefa)
|
| 221 |
+
B, T, d = x.shape
|
| 222 |
+
x_flat, g, mask_ativos = self._computar_gate(x, tarefa)
|
| 223 |
# v8 (item e): computa APENAS os experts ativos num único passe vetorial
|
| 224 |
# (vmap/bmm — Teorema 19.11); na fase densa N = todos. Equivale ao laço
|
| 225 |
# original (Prop. 10.4 preservada por indexação de pesos, não por laço).
|
|
|
|
| 287 |
self.perda_ort = self._ort_intra_grupo(A)
|
| 288 |
return y.reshape(B, T, d)
|
| 289 |
|
| 290 |
+
# ---------------- v19: caminho SonicMoE (opt-in, doc 31 §3) ----------
|
| 291 |
+
def _ffn_tiles(self, esp, xg: torch.Tensor) -> torch.Tensor:
|
| 292 |
+
"""SwiGLU do expert `esp` com GEMM em TILES de linhas do hidden
|
| 293 |
+
(tile-aware — Teo 31.5): o bloco [ini:fim] de w_gate/w_up/w_down fica
|
| 294 |
+
na cache e é reusado por todas as linhas do grupo. tile ≥ d_ff ⟹
|
| 295 |
+
caminho direto (um GEMM — idêntico ao MLPExpert)."""
|
| 296 |
+
wg, wu, wd = esp.w_gate.weight, esp.w_up.weight, esp.w_down.weight
|
| 297 |
+
d_ff = wg.shape[0]
|
| 298 |
+
tm = self.tile_m_sonico
|
| 299 |
+
if tm >= d_ff:
|
| 300 |
+
return esp(xg)
|
| 301 |
+
partes = []
|
| 302 |
+
for ini in range(0, d_ff, tm):
|
| 303 |
+
fim = min(ini + tm, d_ff)
|
| 304 |
+
h = F.silu(F.linear(xg, wg[ini:fim])) * F.linear(xg, wu[ini:fim])
|
| 305 |
+
partes.append(F.linear(h, wd[:, ini:fim]))
|
| 306 |
+
return torch.stack(partes, dim=0).sum(dim=0)
|
| 307 |
+
|
| 308 |
+
def forward_sonico(self, x: torch.Tensor,
|
| 309 |
+
tarefa: str | None = None) -> torch.Tensor:
|
| 310 |
+
"""v19 — execução SONICMOE (opt-in; Teos 31.4/31.5, doc 31).
|
| 311 |
+
|
| 312 |
+
MESMO gate de `_computar_gate` (fonte única). Leitura HONESTA do
|
| 313 |
+
modelo de custo neste código: o gate foco é softmax sobre TODOS os
|
| 314 |
+
experts ativos (todo token tem g_e>0 em todo expert ativo) ⟹
|
| 315 |
+
Σ_e n_e = A·BT — as LINHAS de GEMM são as mesmas do caminho
|
| 316 |
+
vetorial (fator_trabalho = 1). O ganho REAL de IO é:
|
| 317 |
+
(i) eliminação da PILHA de pesos — o caminho vetorial faz
|
| 318 |
+
torch.stack copiando A·3·d_ff·d·4 bytes por forward (escrita
|
| 319 |
+
+ leitura); o caminho sônico lê os pesos de cada expert UMA
|
| 320 |
+
vez, sem cópia;
|
| 321 |
+
(ii) eliminação do tensor YE (A, BT, d) materializado em bloco;
|
| 322 |
+
(iii) TILES — o bloco [ini:fim] das matrizes do expert fica na
|
| 323 |
+
cache e é reusado por todas as linhas do grupo (localidade,
|
| 324 |
+
medida por wall-clock na suíte/scripts).
|
| 325 |
+
Equivalência (Teo 31.4): mesma soma Σ_e g_e·FFN_e(x_t), mesma
|
| 326 |
+
ordem de linhas — diferença só de ordem de acumulação fp; L_lb é
|
| 327 |
+
função só de g (idêntico), L_ort e _Z_ultimo são reconstruídos com
|
| 328 |
+
os MESMOS estimadores (média sobre todas as BT linhas por expert
|
| 329 |
+
ativo) ⟹ forward/refino/telemetria equivalentes ao vetorial."""
|
| 330 |
+
B, T, d = x.shape
|
| 331 |
+
x_flat, g, mask_ativos = self._computar_gate(x, tarefa)
|
| 332 |
+
w = g # (BT, N)
|
| 333 |
+
y = torch.zeros_like(x_flat)
|
| 334 |
+
soma_at = w.sum(dim=1, keepdim=True) # (BT,1)
|
| 335 |
+
linhas_total, experts_ativos = 0, 0
|
| 336 |
+
with torch.no_grad():
|
| 337 |
+
idx_at = mask_ativos.any(dim=0).nonzero(as_tuple=True)[0]
|
| 338 |
+
Z = x_flat.new_zeros(x_flat.shape[0], self.N, d) # (BT,N,d)
|
| 339 |
+
for e in idx_at.tolist():
|
| 340 |
+
esp = self.especialistas[e]
|
| 341 |
+
cols = w[:, e] # (BT,)
|
| 342 |
+
linhas = (cols > 0).nonzero(as_tuple=True)[0]
|
| 343 |
+
if linhas.numel() == 0:
|
| 344 |
+
continue
|
| 345 |
+
experts_ativos += 1
|
| 346 |
+
grupo = x_flat.index_select(0, linhas) # (n_e,d)
|
| 347 |
+
saida = self._ffn_tiles(esp, grupo) # (n_e,d)
|
| 348 |
+
if linhas.numel() == x_flat.shape[0]:
|
| 349 |
+
Z[:, e, :] = saida
|
| 350 |
+
else:
|
| 351 |
+
Z[linhas, e, :] = saida
|
| 352 |
+
y.index_add_(0, linhas, saida * cols[linhas].unsqueeze(-1))
|
| 353 |
+
linhas_total += int(linhas.numel())
|
| 354 |
+
y = y / soma_at.clamp(min=1e-6)
|
| 355 |
+
self._Z_ultimo = Z # (BT,N,d)
|
| 356 |
+
self._g_ultimo = g.mean(dim=0).detach()
|
| 357 |
+
with torch.no_grad():
|
| 358 |
+
p_medio = g.mean(dim=0)
|
| 359 |
+
f_medio = (g > 1.0 / self.N).float().mean(dim=0)
|
| 360 |
+
self.ultimo_p = p_medio.detach()
|
| 361 |
+
self.ultimo_f = f_medio.detach()
|
| 362 |
+
if self.uso_ema is None:
|
| 363 |
+
self.uso_ema = p_medio.detach().clone()
|
| 364 |
+
else:
|
| 365 |
+
self.uso_ema = 0.95 * self.uso_ema + 0.05 * p_medio.detach()
|
| 366 |
+
# L_lb agrupado — idêntico (função só de g)
|
| 367 |
+
lb = x.new_zeros(())
|
| 368 |
+
for gi in range(self.G):
|
| 369 |
+
idx = (self.grupo_de_idx == gi).nonzero(as_tuple=True)[0]
|
| 370 |
+
if idx.numel() == 0:
|
| 371 |
+
continue
|
| 372 |
+
fg = g[:, idx].mean(dim=0)
|
| 373 |
+
pg = g[:, idx].mean(dim=0)
|
| 374 |
+
lb = lb + (fg * pg).sum()
|
| 375 |
+
self.perda_lb = lb / self.G
|
| 376 |
+
# L_ort — MESMOS estimadores do caminho vetorial (média por expert
|
| 377 |
+
# sobre todas as linhas; zeros nos experts inativos)
|
| 378 |
+
acts_full = []
|
| 379 |
+
pos = 0
|
| 380 |
+
for e in range(self.N):
|
| 381 |
+
if pos < idx_at.numel() and int(idx_at[pos]) == e:
|
| 382 |
+
acts_full.append(Z[:, e, :].mean(dim=0))
|
| 383 |
+
pos += 1
|
| 384 |
+
else:
|
| 385 |
+
acts_full.append(torch.zeros(d, device=x.device, dtype=x.dtype))
|
| 386 |
+
A_m = torch.stack(acts_full, dim=1) # (d, N)
|
| 387 |
+
self.perda_ort = self._ort_intra_grupo(A_m)
|
| 388 |
+
# Teo 31.5 — roofline HONESTO (bytes de peso por forward):
|
| 389 |
+
# vetorizado: pilha A·3·d_ff·d·4 (escrita) + leitura da pilha
|
| 390 |
+
# sônico: leitura 1×/expert, SEM pilha, SEM YE em bloco
|
| 391 |
+
bytes_peso_e = 3 * int(self.especialistas[0].w_gate.weight.shape[0]) * d * 4
|
| 392 |
+
S = int(idx_at.numel()) * bytes_peso_e
|
| 393 |
+
linhas_vet = int(idx_at.numel()) * x_flat.shape[0]
|
| 394 |
+
self.ultimo_io = {
|
| 395 |
+
"linhas_vetorizadas": linhas_vet,
|
| 396 |
+
"linhas_grupo": linhas_total,
|
| 397 |
+
"fator_trabalho": linhas_vet / max(1, linhas_total),
|
| 398 |
+
"experts_ativos": experts_ativos,
|
| 399 |
+
"pilha_evitada_bytes": S, # escrita+leitura da stack
|
| 400 |
+
"ganho_io": (2.0 * S + 2.0 * linhas_vet * d * 4)
|
| 401 |
+
/ max(1.0, S + linhas_total * d * 4),
|
| 402 |
+
"tile_m": self.tile_m_sonico}
|
| 403 |
+
return y.reshape(B, T, d)
|
| 404 |
+
|
| 405 |
# ---------------- métricas ----------------
|
| 406 |
def estatisticas(self) -> dict:
|
| 407 |
out = {"fase": "densa" if self.fase_densa else "foco",
|
src/khtst/treino/treinador.py
CHANGED
|
@@ -118,18 +118,43 @@ class TreinadorExtensao(MixinLR, MixinPerdas, MixinEstado, MixinFases):
|
|
| 118 |
self.hub.atributo("jepa/ativo", 1.0)
|
| 119 |
params_jepa = (list(self.motor_jepa.parameters())
|
| 120 |
if self.motor_jepa is not None else None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
# v12 (doc 24) — OTIMIZADOR MuonClip + AdamW (requisito): Muon
|
| 122 |
# (Newton-Schulz, descida espectral — Teo 24.2) nas matrizes do
|
| 123 |
# tronco, AdamW nas tabelas lexicais/vetores (Teo 24.6), QK-clip
|
| 124 |
# pós-step nas atenções NoPE (Teos 24.7/24.8); param_groups padronizados
|
| 125 |
# (ABMO/CIAR/RPP escrevem gp["lr"] — compat v11 integral, Teo 24.9)
|
| 126 |
cfg_opt = dict(getattr(cfg.treino, "otimizador", {}) or {})
|
|
|
|
| 127 |
if cfg_opt.get("ativo", True):
|
| 128 |
from khtst.treino.otimizadores import criar_otimizador_muuonclip
|
| 129 |
self.otimizador = criar_otimizador_muuonclip(modelo, cfg.treino,
|
| 130 |
-
extra_adam=
|
| 131 |
else:
|
| 132 |
-
todos = list(modelo.parameters()) + (params_jepa or [])
|
|
|
|
| 133 |
self.otimizador = torch.optim.AdamW(todos,
|
| 134 |
lr=cfg.treino.lr_max,
|
| 135 |
betas=(0.9, 0.95),
|
|
@@ -601,6 +626,30 @@ class TreinadorExtensao(MixinLR, MixinPerdas, MixinEstado, MixinFases):
|
|
| 601 |
self.hub.atributo("jepa/var_latente",
|
| 602 |
tel_jepa["jepa/var_latente"],
|
| 603 |
self.passo_global)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 604 |
perda_aux = sum(auxiliares) if auxiliares else None
|
| 605 |
# v14 — LOTE EFETIVO por ACUMULAÇÃO DE GRADIENTE (Teo 27.9, requisito
|
| 606 |
# "aumentar lote" sob RAM 4 GiB): cada micro-lote contribui com
|
|
@@ -619,9 +668,15 @@ class TreinadorExtensao(MixinLR, MixinPerdas, MixinEstado, MixinFases):
|
|
| 619 |
if perda_aux is not None and self.cfg.treino.pcgrad:
|
| 620 |
# v15.1: params do motor JEPA no PCGrad (sem isso o gradiente do
|
| 621 |
# caminho JEPA era descartado — smoke 18 achou via drift=0)
|
|
|
|
|
|
|
| 622 |
params_pc = [p for p in self.modelo.parameters() if p.requires_grad]
|
| 623 |
if self.motor_jepa is not None:
|
| 624 |
params_pc += list(self.motor_jepa.parameters())
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
self.ultimos_pcgrad = pcgrad_duas_perdas(params_pc, perda_ce_total,
|
| 626 |
perda_aux)
|
| 627 |
elif perda_aux is not None:
|
|
@@ -687,8 +742,13 @@ class TreinadorExtensao(MixinLR, MixinPerdas, MixinEstado, MixinFases):
|
|
| 687 |
self.hub.atributo(k, float(v), self.passo_global)
|
| 688 |
# v15.1 — JEPA: atualização do alvo DEPOIS do step (ordem obrigatória;
|
| 689 |
# μ pela taxa de sucesso REAL do predictor — Teo 5.6 corrigido)
|
|
|
|
|
|
|
| 690 |
if self.motor_jepa is not None and (self.passo_global % n_acc) == (n_acc - 1):
|
| 691 |
self.motor_jepa.passo_pos_backward()
|
|
|
|
|
|
|
|
|
|
| 692 |
# v12 (doc 24 §§4–5) — QK-CLIP pós-step (MuonClip): escala W_q das
|
| 693 |
# NoPE cujo logit máximo excedeu τ (Teo 24.7 — invariante l_max ≤ τ;
|
| 694 |
# Teo 24.8 — softmax jamais satura; gradiente do passo preservado)
|
|
|
|
| 118 |
self.hub.atributo("jepa/ativo", 1.0)
|
| 119 |
params_jepa = (list(self.motor_jepa.parameters())
|
| 120 |
if self.motor_jepa is not None else None)
|
| 121 |
+
# v19 (doc 31 §2) — CONSELHO JEPA ACOPLADO AOS ENCODERS DE PRODUÇÃO
|
| 122 |
+
# (requisito: "acoplar as variantes JEPA aos encoders de produção").
|
| 123 |
+
# As vistas compartilham os pesos dos encoders REAIS — o gradiente
|
| 124 |
+
# JEPA treina o CodificadorImagem/Audio/Video do modelo. Nascimento
|
| 125 |
+
# neutro: lambda=0 ⟹ conselho não existe e nenhum parâmetro novo.
|
| 126 |
+
cfg_jac = dict(getattr(cfg.treino, "jepa_acoplado", {}) or {})
|
| 127 |
+
self.conselho_acoplado = None
|
| 128 |
+
self.ultima_tel_jac: dict = {}
|
| 129 |
+
if float(cfg_jac.get("lambda", 0.0)) > 0.0:
|
| 130 |
+
from khtst.jepa_familia.acoplamento import construir_conselho_acoplado
|
| 131 |
+
self.conselho_acoplado = construir_conselho_acoplado(
|
| 132 |
+
modelo, cfg_jac, lote_efetivo=max(1, int(cfg.treino.lote_efetivo)))
|
| 133 |
+
self.hub.atributo("jepa_ac/ativo", 1.0)
|
| 134 |
+
# APENAS os parâmetros NOVOS do conselho vão ao grupo extra AdamW:
|
| 135 |
+
# os parâmetros dos ENCODERS já pertencem aos grupos do otimizador
|
| 136 |
+
# principal (via modelo.parameters()) — registrá-los DUAS vezes
|
| 137 |
+
# aplicaria dois passos ao mesmo θ (bug de integração evitado por
|
| 138 |
+
# construção; Teo 31.2-cor).
|
| 139 |
+
params_jac: list = []
|
| 140 |
+
if self.conselho_acoplado is not None:
|
| 141 |
+
ids_modelo = {id(p) for p in modelo.parameters()}
|
| 142 |
+
params_jac = [p for p in self.conselho_acoplado.parameters()
|
| 143 |
+
if id(p) not in ids_modelo and p.requires_grad]
|
| 144 |
# v12 (doc 24) — OTIMIZADOR MuonClip + AdamW (requisito): Muon
|
| 145 |
# (Newton-Schulz, descida espectral — Teo 24.2) nas matrizes do
|
| 146 |
# tronco, AdamW nas tabelas lexicais/vetores (Teo 24.6), QK-clip
|
| 147 |
# pós-step nas atenções NoPE (Teos 24.7/24.8); param_groups padronizados
|
| 148 |
# (ABMO/CIAR/RPP escrevem gp["lr"] — compat v11 integral, Teo 24.9)
|
| 149 |
cfg_opt = dict(getattr(cfg.treino, "otimizador", {}) or {})
|
| 150 |
+
extra_adam_total = (list(params_jepa) if params_jepa else []) + params_jac
|
| 151 |
if cfg_opt.get("ativo", True):
|
| 152 |
from khtst.treino.otimizadores import criar_otimizador_muuonclip
|
| 153 |
self.otimizador = criar_otimizador_muuonclip(modelo, cfg.treino,
|
| 154 |
+
extra_adam=extra_adam_total or None)
|
| 155 |
else:
|
| 156 |
+
todos = (list(modelo.parameters()) + (params_jepa or [])
|
| 157 |
+
+ params_jac)
|
| 158 |
self.otimizador = torch.optim.AdamW(todos,
|
| 159 |
lr=cfg.treino.lr_max,
|
| 160 |
betas=(0.9, 0.95),
|
|
|
|
| 626 |
self.hub.atributo("jepa/var_latente",
|
| 627 |
tel_jepa["jepa/var_latente"],
|
| 628 |
self.passo_global)
|
| 629 |
+
# v19 (doc 31 §2) — perda do CONSELHO ACOPLADO sobre os lotes crus
|
| 630 |
+
# das modalidades presentes no micro-lote (mesma _aux_norm — Teo 30.3
|
| 631 |
+
# anti-dominação). A guard de ordem EMA por variante usa a tolerância
|
| 632 |
+
# = lote_efetivo (injetada na construção — Teo 31.6).
|
| 633 |
+
if self.conselho_acoplado is not None:
|
| 634 |
+
lotes_j = {}
|
| 635 |
+
for mod in ("imagem", "audio", "video"):
|
| 636 |
+
if mod in extra and extra[mod] is not None:
|
| 637 |
+
lotes_j[mod] = extra[mod]
|
| 638 |
+
if lotes_j:
|
| 639 |
+
out_j = self.conselho_acoplado.perda(lotes_j)
|
| 640 |
+
if float(out_j.get("ativas", 0.0)) > 0.0 \
|
| 641 |
+
and torch.is_tensor(out_j["loss"]):
|
| 642 |
+
lam_jac = float(dict(getattr(self.cfg.treino, "jepa_acoplado",
|
| 643 |
+
{}) or {}).get("lambda", 0.0))
|
| 644 |
+
auxiliares.append(self._aux_norm("jepa_ac", lam_jac,
|
| 645 |
+
out_j["loss"]))
|
| 646 |
+
self.ultima_tel_jac = out_j
|
| 647 |
+
if self.passo_global % 50 == 0:
|
| 648 |
+
self.hub.atributo("jepa_ac/perda",
|
| 649 |
+
float(out_j["loss"]), self.passo_global)
|
| 650 |
+
for k, v in self.conselho_acoplado.telemetria().items():
|
| 651 |
+
if isinstance(v, (int, float)):
|
| 652 |
+
self.hub.atributo(k, float(v), self.passo_global)
|
| 653 |
perda_aux = sum(auxiliares) if auxiliares else None
|
| 654 |
# v14 — LOTE EFETIVO por ACUMULAÇÃO DE GRADIENTE (Teo 27.9, requisito
|
| 655 |
# "aumentar lote" sob RAM 4 GiB): cada micro-lote contribui com
|
|
|
|
| 668 |
if perda_aux is not None and self.cfg.treino.pcgrad:
|
| 669 |
# v15.1: params do motor JEPA no PCGrad (sem isso o gradiente do
|
| 670 |
# caminho JEPA era descartado — smoke 18 achou via drift=0)
|
| 671 |
+
# v19: + parâmetros NOVOS do conselho acoplado (os encoders já
|
| 672 |
+
# estão em modelo.parameters())
|
| 673 |
params_pc = [p for p in self.modelo.parameters() if p.requires_grad]
|
| 674 |
if self.motor_jepa is not None:
|
| 675 |
params_pc += list(self.motor_jepa.parameters())
|
| 676 |
+
if self.conselho_acoplado is not None:
|
| 677 |
+
ids_modelo = {id(p) for p in self.modelo.parameters()}
|
| 678 |
+
params_pc += [p for p in self.conselho_acoplado.parameters()
|
| 679 |
+
if id(p) not in ids_modelo and p.requires_grad]
|
| 680 |
self.ultimos_pcgrad = pcgrad_duas_perdas(params_pc, perda_ce_total,
|
| 681 |
perda_aux)
|
| 682 |
elif perda_aux is not None:
|
|
|
|
| 742 |
self.hub.atributo(k, float(v), self.passo_global)
|
| 743 |
# v15.1 — JEPA: atualização do alvo DEPOIS do step (ordem obrigatória;
|
| 744 |
# μ pela taxa de sucesso REAL do predictor — Teo 5.6 corrigido)
|
| 745 |
+
# v19 — conselho acoplado: EMA de cada variante ATIVA no grupo de
|
| 746 |
+
# micro-lotes (mesma ordem pós-step — Teo 31.6)
|
| 747 |
if self.motor_jepa is not None and (self.passo_global % n_acc) == (n_acc - 1):
|
| 748 |
self.motor_jepa.passo_pos_backward()
|
| 749 |
+
if self.conselho_acoplado is not None \
|
| 750 |
+
and (self.passo_global % n_acc) == (n_acc - 1):
|
| 751 |
+
self.conselho_acoplado.passo_pos_backward()
|
| 752 |
# v12 (doc 24 §§4–5) — QK-CLIP pós-step (MuonClip): escala W_q das
|
| 753 |
# NoPE cujo logit máximo excedeu τ (Teo 24.7 — invariante l_max ≤ τ;
|
| 754 |
# Teo 24.8 — softmax jamais satura; gradiente do passo preservado)
|
tests/test_khtst_v19.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
"""Suíte v19 — acoplamento JEPA↔encoders de produção + SonicMoE no tronco
|
| 3 |
+
(doc 31). Cada verificação cita o teorema/regra que prova a propriedade."""
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import math
|
| 7 |
+
import os
|
| 8 |
+
import sys
|
| 9 |
+
|
| 10 |
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
|
| 11 |
+
|
| 12 |
+
import torch # noqa: E402
|
| 13 |
+
|
| 14 |
+
PASSOS = []
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def check(nome: str, cond, detalhe: str = ""):
|
| 18 |
+
PASSOS.append((nome, bool(cond), detalhe))
|
| 19 |
+
print(f" {'✓' if cond else '✗'} {nome}" + (f" — {detalhe}" if detalhe else ""))
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def main() -> int:
|
| 23 |
+
torch.manual_seed(0)
|
| 24 |
+
print("=== SUÍTE v19 — acoplamento JEPA/SonicMoE (doc 31) ===")
|
| 25 |
+
|
| 26 |
+
# ============================== §1 SonicMoE (Teos 31.3–31.5) ==========
|
| 27 |
+
from khtst.percepcao.moe import MoEAgrupavel
|
| 28 |
+
moe = MoEAgrupavel(d=64, n_grupos=3, experts_por_grupo=2, d_ff_expert=48,
|
| 29 |
+
top_k=2, n_tarefas=12, vetorial=True)
|
| 30 |
+
x = torch.randn(2, 8, 64, requires_grad=True)
|
| 31 |
+
moe.fase_densa = False
|
| 32 |
+
y_vet = moe(x, tarefa="conversa")
|
| 33 |
+
lb_vet = moe.perda_lb.detach().clone()
|
| 34 |
+
ort_vet = moe.perda_ort.detach().clone()
|
| 35 |
+
g_vet = moe._g_ultimo.clone()
|
| 36 |
+
Z_vet = moe._Z_ultimo.clone()
|
| 37 |
+
u = torch.randn_like(y_vet)
|
| 38 |
+
(y_vet * u).sum().backward()
|
| 39 |
+
gx_vet = x.grad.clone()
|
| 40 |
+
gw_vet = {n: (p.grad.clone() if p.grad is not None else None)
|
| 41 |
+
for n, p in moe.named_parameters()}
|
| 42 |
+
# caminho sônico — MESMOS pesos
|
| 43 |
+
for p in moe.parameters():
|
| 44 |
+
p.grad = None
|
| 45 |
+
x.grad = None
|
| 46 |
+
moe.ativar_sonico(tile_m=16)
|
| 47 |
+
y_son = moe(x, tarefa="conversa")
|
| 48 |
+
dy = (y_vet - y_son).abs().max().item()
|
| 49 |
+
check("SonicMoE: saída ≡ vetorial (Teo 31.4)", dy < 1e-5, f"Δy={dy:.2e}")
|
| 50 |
+
check("SonicMoE: gate bit-idêntico (fonte única do gate — Teo 31.4)",
|
| 51 |
+
(g_vet - moe._g_ultimo).abs().max().item() == 0.0)
|
| 52 |
+
check("SonicMoE: _Z_ultimo ≡ (refino entre-MoEs intacto — Teo 31.4)",
|
| 53 |
+
(Z_vet - moe._Z_ultimo).abs().max().item() < 1e-5)
|
| 54 |
+
check("SonicMoE: L_lb idêntico (função só de g)",
|
| 55 |
+
(lb_vet - moe.perda_lb.detach()).abs().max().item() == 0.0)
|
| 56 |
+
check("SonicMoE: L_ort ≡ (mesmos estimadores — Teo 31.4)",
|
| 57 |
+
(ort_vet - moe.perda_ort.detach()).abs().max().item() < 1e-6)
|
| 58 |
+
(y_son * u).sum().backward()
|
| 59 |
+
dgx = (gx_vet - x.grad).abs().max().item()
|
| 60 |
+
check("SonicMoE: gradiente da ENTRADA ≡ (Teo 31.4-cor)", dgx < 1e-5,
|
| 61 |
+
f"Δ={dgx:.2e}")
|
| 62 |
+
worst = 0.0
|
| 63 |
+
for n, g in gw_vet.items():
|
| 64 |
+
if g is None:
|
| 65 |
+
continue
|
| 66 |
+
worst = max(worst, (g - dict(moe.named_parameters())[n].grad).abs().max().item())
|
| 67 |
+
check("SonicMoE: gradiente dos PESOS ≡ (Teo 31.4-cor)", worst < 1e-5,
|
| 68 |
+
f"Δ={worst:.2e}")
|
| 69 |
+
io = moe.ultimo_io
|
| 70 |
+
check("SonicMoE: telemetria IO com pilha_evitada (Teo 31.5)",
|
| 71 |
+
io.get("pilha_evitada_bytes", 0) > 0 and io.get("ganho_io", 0) >= 1.0,
|
| 72 |
+
f"pilha={io.get('pilha_evitada_bytes')} ganho={io.get('ganho_io')}")
|
| 73 |
+
check("SonicMoE: fator_trabalho honesto (softmax sobre ativos ⟹ linhas "
|
| 74 |
+
"iguais — Teo 31.5-nota)", io.get("fator_trabalho") == 1.0)
|
| 75 |
+
# tiles ≥ d_ff ⟹ caminho direto
|
| 76 |
+
y_son2 = moe._ffn_tiles(moe.especialistas[0], torch.randn(4, 64))
|
| 77 |
+
esp0 = moe.especialistas[0](torch.randn(4, 64))
|
| 78 |
+
check("SonicMoE: tiles são FFN SwiGLU (forma — Teo 31.5)",
|
| 79 |
+
y_son2.shape == esp0.shape)
|
| 80 |
+
# nascimento neutro
|
| 81 |
+
moe2 = MoEAgrupavel(d=64, n_grupos=3, experts_por_grupo=2, d_ff_expert=48,
|
| 82 |
+
top_k=2, n_tarefas=12)
|
| 83 |
+
check("SonicMoE: nascimento NEUTRO (modo_sonico=False — Teo 31.1)",
|
| 84 |
+
moe2.modo_sonico is False and moe2.ultimo_io == {})
|
| 85 |
+
|
| 86 |
+
# ============================== §2 acoplamento (Teos 31.1–31.2) =======
|
| 87 |
+
from khtst.config import Config
|
| 88 |
+
from khtst.jepa_familia.acoplamento import (construir_conselho_acoplado,
|
| 89 |
+
VistaTokensImagem,
|
| 90 |
+
VistaTokensAudio,
|
| 91 |
+
VistaFramesVideo)
|
| 92 |
+
from khtst.nucleo.modelo import KHTSTModel
|
| 93 |
+
cfg = Config()
|
| 94 |
+
cfg.modelo.escalacao["ativo"] = False
|
| 95 |
+
modelo = KHTSTModel(cfg, usar_multimodal=True)
|
| 96 |
+
check("modelo: sonicmoe desligado por padrão (nascimento neutro — Teo 31.1)",
|
| 97 |
+
modelo.sonicmoe_ativo is False)
|
| 98 |
+
cfg.modelo.sonicmoe["ativo"] = True
|
| 99 |
+
modelo2 = KHTSTModel(cfg, usar_multimodal=True)
|
| 100 |
+
check("modelo: sonicmoe.ativo=true liga o caminho no TRONCO (requisito)",
|
| 101 |
+
modelo2.sonicmoe_ativo is True,
|
| 102 |
+
f"camadas={[b.moe.modo_sonico for b in modelo2.blocos if b.moe is not None and hasattr(b.moe, 'modo_sonico')]}"[:110])
|
| 103 |
+
cfg.modelo.sonicmoe["ativo"] = False
|
| 104 |
+
|
| 105 |
+
# gradiente compartilhado (Teo 31.2): perda JEPA grafia nos pesos REAIS
|
| 106 |
+
conselho = construir_conselho_acoplado(
|
| 107 |
+
modelo, {"lambda": 0.3, "variantes": ["imagem", "audio"],
|
| 108 |
+
"pred_hidden": 128}, lote_efetivo=2)
|
| 109 |
+
check("conselho acoplado: variantes criadas para os encoders presentes",
|
| 110 |
+
set(conselho.nomes) == {"audio", "imagem"})
|
| 111 |
+
w0 = modelo.enc_imagem.proj.weight.data.clone()
|
| 112 |
+
out = conselho.perda({"imagem": torch.rand(2, 3, 96, 96)})
|
| 113 |
+
out["loss"].backward()
|
| 114 |
+
check("Teo 31.2: gradiente JEPA flui para o ENCODER DE PRODUÇÃO",
|
| 115 |
+
modelo.enc_imagem.proj.weight.grad is not None
|
| 116 |
+
and modelo.enc_imagem.proj.weight.grad.abs().sum().item() > 0)
|
| 117 |
+
# sem grad no alvo EMA
|
| 118 |
+
tgt = conselho.variantes["imagem"].target
|
| 119 |
+
check("Teo 31.2: alvo EMA NÃO recebe gradiente (requires_grad=False)",
|
| 120 |
+
all(not p.requires_grad for p in tgt.parameters()))
|
| 121 |
+
# vista ≡ tokens do encoder (mesmos pesos — Teo 31.2)
|
| 122 |
+
modelo.eval()
|
| 123 |
+
with torch.no_grad():
|
| 124 |
+
img = torch.rand(1, 3, 96, 96)
|
| 125 |
+
vis = VistaTokensImagem(modelo.enc_imagem)(img)
|
| 126 |
+
x0 = modelo.enc_imagem.proj(img).flatten(2).transpose(1, 2)
|
| 127 |
+
check("vista de imagem: (B, 1+n, d_enc) com d_enc do encoder",
|
| 128 |
+
vis.shape[0] == 1 and vis.shape[2] == modelo.enc_imagem.proj.out_channels
|
| 129 |
+
and vis.shape[1] == x0.shape[1] + 1,
|
| 130 |
+
f"forma={tuple(vis.shape)}")
|
| 131 |
+
onda = torch.randn(1, 16000)
|
| 132 |
+
va = VistaTokensAudio(modelo.enc_audio)(onda)
|
| 133 |
+
check("vista de áudio: tokens (B, T', d_enc) do encoder de produção",
|
| 134 |
+
va.shape[2] == modelo.enc_audio.conv2.out_channels
|
| 135 |
+
and va.shape[0] == 1, f"forma={tuple(va.shape)}")
|
| 136 |
+
vid = torch.rand(1, 3, 3, 96, 96)
|
| 137 |
+
vv = VistaFramesVideo(modelo.enc_video)(vid)
|
| 138 |
+
check("vista de vídeo: CLS por frame (B, t, d_enc)",
|
| 139 |
+
vv.shape[0] == 1 and vv.shape[1] == 3
|
| 140 |
+
and vv.shape[2] == modelo.enc_video.saida.in_features)
|
| 141 |
+
# guarda de ordem EMA (Teo 31.6) — RuntimeError na violação
|
| 142 |
+
conselho_g = construir_conselho_acoplado(
|
| 143 |
+
modelo, {"lambda": 0.3, "variantes": ["imagem"], "pred_hidden": 128},
|
| 144 |
+
lote_efetivo=1)
|
| 145 |
+
violou = False
|
| 146 |
+
try:
|
| 147 |
+
for _ in range(4):
|
| 148 |
+
conselho_g.perda({"imagem": torch.rand(2, 3, 96, 96)})
|
| 149 |
+
except RuntimeError:
|
| 150 |
+
violou = True
|
| 151 |
+
check("Teo 31.6: guarda de ordem EMA LANÇA RuntimeError na violação",
|
| 152 |
+
violou)
|
| 153 |
+
conselho_g.passo_pos_backward() # restaura a ordem
|
| 154 |
+
# contrato do treinador: passo_pos_backward zera o contador (Teo 31.6)
|
| 155 |
+
v = conselho_g.variantes["imagem"]
|
| 156 |
+
check("Teo 31.6: passo_pos_backward reinicia o contador da guarda",
|
| 157 |
+
v._passos_sem_ema == 0)
|
| 158 |
+
# pareamento A/B (Teo 31.7): perda JEPA não consome o RNG global
|
| 159 |
+
img_ab = torch.rand(2, 3, 96, 96) # lote criado ANTES do snapshot
|
| 160 |
+
st0 = torch.get_rng_state()
|
| 161 |
+
with torch.no_grad():
|
| 162 |
+
conselho_g.perda({"imagem": img_ab})
|
| 163 |
+
check("Teo 31.7: perda JEPA NÃO consome o RNG global (gerador dedicado)",
|
| 164 |
+
torch.equal(st0, torch.get_rng_state()))
|
| 165 |
+
# nascimento neutro do conselho (Teo 31.1): λ=0 ⟹ sem conselho
|
| 166 |
+
from khtst.treino.treinador import TreinadorExtensao # noqa: E402 (fumaça)
|
| 167 |
+
check("Teo 31.1: gate do conselho nasce UNIFORME (nascimento neutro)",
|
| 168 |
+
torch.allclose(conselho.pesos(),
|
| 169 |
+
torch.full((2,), 0.5), atol=1e-6))
|
| 170 |
+
|
| 171 |
+
# ============================== §3 config + documentação ==============
|
| 172 |
+
import json as _json
|
| 173 |
+
base = _json.load(open(os.path.join(os.path.dirname(__file__), "..",
|
| 174 |
+
"configs", "base.json")))
|
| 175 |
+
check("config: modelo.sonicmoe presente e DESLIGADO (opt-in)",
|
| 176 |
+
base["modelo"].get("sonicmoe", {}).get("ativo") is False)
|
| 177 |
+
check("config: treino.jepa_acoplado presente e λ=0 (opt-in)",
|
| 178 |
+
base["treino"].get("jepa_acoplado", {}).get("lambda") == 0.0)
|
| 179 |
+
doc = os.path.join(os.path.dirname(__file__), "..", "docs", "matematica",
|
| 180 |
+
"31-v19-acoplamento-jepa-sonicmoe.md")
|
| 181 |
+
check("doc 31 existe com provas", os.path.exists(doc))
|
| 182 |
+
if os.path.exists(doc):
|
| 183 |
+
txt = open(doc, encoding="utf-8").read()
|
| 184 |
+
for teo in ("Teo 31.1", "Teo 31.2", "Teo 31.4", "Teo 31.5",
|
| 185 |
+
"Teo 31.6", "Teo 31.7"):
|
| 186 |
+
check(f"doc 31 cita {teo}", teo in txt)
|
| 187 |
+
# replay/hardware não-regredidos (Teo 30.14/30.15 intactos)
|
| 188 |
+
from khtst.jepa_familia.replayssm import ReplaySSM
|
| 189 |
+
from khtst.jepa_familia.dispositivo import resolver_dispositivo
|
| 190 |
+
rr = ReplaySSM(capacidade=10)
|
| 191 |
+
rr.registrar(torch.zeros(2, 4, 8))
|
| 192 |
+
check("ReplaySSM: interface v18 intacta", rr._n_vistos == 1)
|
| 193 |
+
disp = resolver_dispositivo()
|
| 194 |
+
check("hardware: escolha de dispositivo v18 intacta (Teo 30.15)",
|
| 195 |
+
disp is not None)
|
| 196 |
+
|
| 197 |
+
# ============================== resultado =============================
|
| 198 |
+
ok = sum(1 for _, c, _ in PASSOS if c)
|
| 199 |
+
total = len(PASSOS)
|
| 200 |
+
print("=" * 72)
|
| 201 |
+
print(f"SUITE v19: {ok}/{total} verificações verdes"
|
| 202 |
+
+ (" — TODAS VERDES ✓" if ok == total else f" — {total - ok} FALHAS"))
|
| 203 |
+
return 0 if ok == total else 1
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
if __name__ == "__main__":
|
| 207 |
+
raise SystemExit(main())
|