PowerMachine commited on
Commit
546119a
·
verified ·
1 Parent(s): 5e2c860

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 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 (v17)
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) | ⏳ planejado §5 | — |
49
- | ReplaySSM | ⏳ planejado §5 | — |
50
- | Image-JEPA/V-JEPA 2 (espaço/pixel-block) | ⏳ planejado §5 | MC-JEPA (VICReg Cython) já é o alicerce |
51
- | Audio-JEPA / Point-JEPA / 3D-JEPA | ⏳ planejado §5 | encoders já existem (audio.py, profundidade.py) |
52
  | Brain-JEPA (fMRI/EEG) | ⏳ parcial | guarda do passo pronta (Teo 28.10); falta dataset |
53
- | T-JEPA / LeJEPA / BJEPA | ⏳ planejado §5 | — |
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–30)
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 v19):
 
 
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 — Cython total dos hot-paths**: conv1d seletiva do Mamba-3 e
 
 
 
 
 
 
158
  softmax top-k do roteador em Cython/OpenMP (prioridade Cython do
159
  requisito); detecção automática CPU/GPU mantida.
160
- 5. **v20 — Integração da tabela v17 no treinador**: ligar
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
- 6. **v18 ✓ — Hardware chinês** (ENTREGUE: `jepa_familia/dispositivo.py` —
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

  • SHA256: d314baabfae7c9d1f7f46c84502b0243beb4dfeb2d915285c704a8a5fb23176d
  • Pointer size: 131 Bytes
  • Size of remote file: 190 kB
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
- return self.funil_bar.atualizar(self.funil)
 
 
 
 
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/.venv/lib/python3.12/site-packages/numpy/_core/include"
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:a479d9e6c2c38330c5742fe3935ded3205ffaead794f06dfc8aa1d6683b6a546
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:758e2891ee47f683f072e1cd7f7be0f11644ab9cdca84151a1a219b16d276f66
3
- size 1320080
 
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:d98748e400832ca3d07408ab0c31375b46eb67a89f44b468b065787a731dfbec
3
- size 1602848
 
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
- idx = torch.randperm(n, device=tokens.device)
 
 
 
 
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
- # ---------------- forward ----------------
140
- def forward(self, x: torch.Tensor, tarefa: str | None = None) -> torch.Tensor:
141
- """x: (B, T, d) → saída (B, T, d) + perdas auxiliares atualizadas."""
 
 
 
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=params_jepa)
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())