diff --git a/Cargo.lock b/Cargo.lock index 671a2e95eabb9adff99c9c789c199de61842b472..709aab8cb9cd4b9abaa26d9980bc8d24ad1d31e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -723,7 +723,7 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "khtst-rust" -version = "0.9.0" +version = "0.10.0" dependencies = [ "anyhow", "built", diff --git a/Cargo.toml b/Cargo.toml index 197407abe8cd4507a0f1dbd0e3252028c407ac8d..0533fb95546663d69cff93a709b62025672c10ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "khtst-rust" -version = "0.9.0" +version = "0.10.0" edition = "2021" -description = "KHTST_RUST — análise/prova/teste das críticas Qwen3.8 v2 (r-34): skew O(n²) diferenciável via index_add, calibrar vetorial bit-a-bit, forward canônico (bug v080-1 fora da API pública), FFI isolada em feature python, newtypes de domínio" +description = "KHTST_RUST — críticas Qwen3.8 lote 3 (r-35): divisão SRP dos monólitos, checkpoints atômicos SHA-256+fsync+rename, newtype TemperaturaInversa (fronteira tipada N-2), trait CamadaKhtst + CamadaHomeostatica, CLI unificada Typer" license = "Apache-2.0" [lib] diff --git a/docs/memorias/r-35-analise-provas-qwen-v010.md b/docs/memorias/r-35-analise-provas-qwen-v010.md new file mode 100644 index 0000000000000000000000000000000000000000..0215ac4cef3dad1550c73637f4c4f1876cac5185 --- /dev/null +++ b/docs/memorias/r-35-analise-provas-qwen-v010.md @@ -0,0 +1,360 @@ +# r-35 — Análise, Provas e Testes das Críticas de Engenharia do Agente Qwen3.8 (v0.10.0, lote 3) + +**Escopo**: o usuário pediu *analisar, provar e testar* o terceiro lote de +críticas do agente Qwen3.8 (anti-padrões de engenharia de software que +"comprometem a escalabilidade": monólitos Rust, scripts Python +sequenciais, checkpointing não-atômico, invariantes apenas em +comentários), mantendo as regras da casa: **nenhuma funcionalidade SOTA +eficaz é removida**; toda mudança com prova matemática, lógica e de +lógica computacional; provas em markdown; validação com dataset real; +autocrítica (inclusive das minhas próprias correções). + +**Metodologia** (herdada da r-34): (i) verificação simbólica/lógica de +cada crítica e proposta ANTES do código; (ii) confronto com oráculos +independentes (medições frias em target dir separado, experimento +controlado com grupo de controle, vetores oficiais FIPS 180-4); (iii) +implementação apenas do que sobrevive a (i)+(ii), como **adição**; (iv) +testes de regressão + suíte consolidada; (v) validação fim-a-fim com +dataset real (Wikipédia PT-BR, streaming controlado). + +**Veredito sumário**: + +| # | Crítica / Proposta | Veredito | Ação v0.10.0 | Ganho medido | +|---|--------------------|----------|--------------|--------------| +| 1 | Monólitos jepa.rs (58,4 kB), pensamento.rs (53,4 kB), moe_redirecionavel.rs (50,6 kB) violam SRP; "Lei de Amdahl p/ compilação": split ⇒ T_inc ≈ O(N/k) | **SRP VÁLIDO; a "prova" é uma APLICAÇÃO ERRADA de Amdahl e a alegação O(N/k) é REFUTADA por experimento controlado** | divisão em 26 submódulos coesos com re-exports (API estável); +python.rs 112 kB (pior monólito — acrescentado por autocrítica) | 61 arquivos, maior agora 57,6 kB (fnh.rs); 107/107 gates herdados preservados; compilação incremental ~1,1 s ANTES E DEPOIS (ruído); builds frios neutros (70→68 s, 85→86 s) | +| 2 | "Scripts Python sequenciais" (39_coletar…74) ⇒ substituir por CLI Typer | **PARCIALMENTE VÁLIDA como desejo de unificação; a SUBSTITUIÇÃO é REJEITADA** (quebra rastreabilidade r-XX↔scripts e remove funcionalidade) | `scripts/75_cli.py` — fachada Typer que delega aos scripts canônicos + política de resume com integridade + gestão de estados | 1 entry point p/ todo o pipeline; resume com prova SHA-256; drill de corrupção 3/3 | +| 3 | Checkpointing não-atômico (`os.path.exists` vulnerável a queda de energia) | **VÁLIDA no problema; a proposta tem 3 falhas** (destrói o estado anterior; manifesto sem fsync; bincode+`unwrap`) | `src/checkpoint.rs`: SHA-256 puro (FIPS 180-4) + tmp→fsync→rename→fsync(dir) p/ conteúdo E manifesto; `carregar_verificado`; bindings Python | 2 checkpoints do treino real verificados (240,9 MB e 59,5 MB, manifesto 141 B); adulteração/truncamento/manifesto-trocado detectados; estado anterior preservado sob falha simulada | +| 4 | "Invariantes apenas em comentários" (convexidade em γ=1/T) ⇒ newtype `TemperaturaInversa` | **VÁLIDA no padrão; a alegação "garantia em tempo de COMPILAÇÃO" é FALSA** (a validação é de runtime na fronteira) | `tipos::TemperaturaInversa` (T-V3) + fronteira tipada no verificador (`nll_temperatura_tipado`, setter/getter, saída do otimizador validada) | T* do ajuste validada por construção; equivalência bit-a-bit com o caminho cru (tv3d); pré-condição do Teo N-2 carregada no tipo | +| P1 | Trait `CamadaKhtst` c/ `forward`+`parametros`+`gate_ativacao` | **IDEIA VÁLIDA; PREMISSA FALSA (controlador NÃO conhece Fnh/Moe/Jepa — verificado no fonte) e assinatura quebraria o otimizador** | trait corrigido (`Vec`; gate segregado em `CamadaHomeostatica`; adaptador `MoeComPapel` captura o papel) | composição heterogênea preserva B×T (CK-1); delegação bit-a-bit (CK-1c); despacho homeostático tipado (CK-2a) | +| P3 | Checkpoint atômico com "prova criptográfica" | ver #3 acima | idem | idem | +| P4 | CLI Typer declarativo | ver #2 acima | idem | idem | + +**Contagem**: 4 críticas + 4 propostas analisadas; **2 aceitas com +correções substanciais**, **1 aceita parcialmente (substância sim, +substituição não)**, **1 com premissa refutada por experimento**, e **todas +as 4 propostas de código continham erros** catalogados abaixo. 16 testes +novos; suíte consolidada **107/107**; cargo **123/123**. + +--- + +## §1 — Ambiente, baseline e higiene (pré-requisitos do usuário) + +- **Processos de fundo**: nenhum processo residual de treino encontrado + (`ps aux` na abertura); toda execução em primeiro plano, como exige a + advertência de que o fundo mata processos sem erro/OOM (confirmado + empiricamente nas sessões anteriores — r-32 §"fase C com resume"). +- **Estados de treino**: varredura inicial (`*.pt`, `*.npz`, `*.ckpt`, + `*.safetensors`, `*.estado.json`) = **0 estados locais**; um + `telemetria_fase_c.jsonl` da v0.9.0 (estado residual de treino + anterior, exatamente o que o usuário manda procurar) foi **encontrado + e apagado** antes do treino fresco v0.10.0 (§5). +- **Espaço**: 3,1 GB de alvos cargo obsoletos (`logs/tgt_full`, + `logs/tgt_domain` — artefatos de builds de sessões passadas) apagados + (96% → 64% de uso do disco). +- **Baseline**: v0.9.0 reconstruída e verificada **107/107** antes de + qualquer mudança. + +## §2 — Crítica 1: "Monólitos em Rust" e a "Lei de Amdahl para Compilação" + +### 2.1 Análise da crítica + +**Válido (substância)**: `jepa.rs` (58.407 B), `pensamento.rs` (53.408 B) +e `moe_redirecionavel.rs` (50.941 B) eram arquivos multi-responsabilidade +— `jepa.rs` continha núcleo E/P/EMA, 6 famílias de máscara, 6 +embaralhamentos de patches, enum de 10 variantes + treinos especializados, +SonicMoE E ReplaySSM. Violação real de SRP. + +**Inválido (rótulo)**: a "Prova Lógica (Lei de Amdahl para Compilação)" +não é a Lei de Amdahl. A Lei de Amdahl limita o ganho de paralelização +pela fração serial: S(n) = 1/((1−p) + p/n). O que a crítica quer invocar +é a granularidade da **unidade de compilação** — e a afirmação +"T_inc do monólito é O(N); dividir em módulos reduz T_inc ≈ O(N/k)" é +**falsa na granularidade proposta**: + +**Teo CK-0 (granularidade de recompilação).** O cargo/rustc recompila por +**crate**, não por arquivo. Dividir `jepa.rs` em `jepa/*.rs` não cria +unidades de compilação novas: um toque em qualquer arquivo do diretório +recompila o crate inteiro (com reuso incremental por queries/itens). Para +obter T_inc ≈ O(N/k) seria necessário dividir em **k crates** (workspace), +o que tem custos próprios: unificação de features entre crates, a `cdylib` +única do PyO3 exigiria um crate só de FFI dependendo de crates de domínio, +e qualquer mudança de API pública recompila os dependentes (o grafo de +dependências domina — de novo Amdahl, agora corretamente aplicado: o ganho +é limitado pelo caminho crítico do DAG de crates). ∎ + +### 2.2 O que foi feito (divisão SRP aditiva) + +- `jepa.rs` → `jepa/{nucleo, mascaras, patches, variantes, sonic_moe, + replay_ssm, testes}.rs` +- `pensamento.rs` → `pensamento/{motor, combinador, quebra_cabeca, testes}.rs` +- `moe_redirecionavel.rs` → `moe_redirecionavel/{atencao, nucleo, treino, + testes, teste_integrado}.rs` +- `python.rs` (112.410 B — o **pior** monólito, não citado pela crítica: + errata por autocrítica) → `python/{nucleo, treino_fases, som, + jepa_treinos, pensamento_demos, calibracao, moe, fnh_arvore, nlp_nlg, + demos_qwen}.rs` +- Cada `mod.rs` re-exporta `pub use sub::*;` ⇒ **todos os caminhos + `crate::jepa::X` etc. permanecem válidos** (107/107 testes herdados + verdes = prova de preservação de comportamento). +- Submódulos do python/ ganham `pub(crate)` nas pyfunctions/pyclasses (o + registro `#[pymodule]` referencia `sub::item`); helpers entre irmãos + (`distancia_eucl`, `carregar_tokenizador`, `ConfigPy`) promovidos a + `pub(crate)` — visibilidade mínima necessária, nada vira `pub`. + +**Aritmética da divisão**: 4 monólitos (275,2 kB) → 26 arquivos +(maior submódulo: `python/demos_qwen.rs`; maior arquivo do projeto agora +`fnh.rs` com 57,6 kB — abaixo do menor monólito criticado). + +### 2.3 Experimento controlado (refutação da alegação O(N/k)) ∎ + +**Protocolo**: build aquecido; toque único (`os.utime`) no arquivo; +`cargo build --no-default-features`; mediana de 3 execuções. Grupo de +controle: `tipos.rs` (arquivo pequeno, inalterado pelo split). + +| arquivo tocado | v0.9.0 (monólito) | v0.10.0 (submódulo) | +|---|---|---| +| jepa (58,4 kB) | 1,06 s | 1,13 s (`jepa/variantes.rs`) | +| pensamento (53,4 kB) | 1,13 s | 1,15 s (`pensamento/motor.rs`) | +| moe (50,9 kB) | 1,05 s | 1,20 s (`moe/nucleo.rs`) | +| **controle** tipos.rs | 1,21 s | 1,19 s | + +O controle varia tanto quanto os alvos (±0,15 s) — **o custo incremental +é dominado pelo overhead fixo do rustc/cargo, não pelo tamanho do arquivo +modificado**. A alegação O(N/k) é refutada nesta granularidade. Builds +frios (target dir separado): domínio 70→68 s, completo 85→86 s — +**neutros**; a divisão não degradou nada e o ganho é o SRP. + +### 2.4 O ganho real de compilação já estava capturado + +A separação que MUDA o tempo de compilação é a de **features** (v0.9.0, +r-34 §5): domínio testa sem linkar Python. Overhead Python+ponte medido +agora: 22,0% (68 s vs 86 s frios) — consistente com os 17,6% da r-34 +(mesmo protocolo, máquina com carga diferente). + +## §3 — Proposta 1: trait `CamadaKhtst` (polimorfismo de camadas) + +### 3.1 Análise e erratas da proposta + +1. **Premissa falsa** (verificada no fonte): `rg "fnh|moe|jepa" src/controlador.rs` + = **0 ocorrências**. O `Controlador` regula hiperparâmetros + (AutoAjustePensamento/Ucb1/AutoAjusteSom); ele NÃO conhece camadas + concretas. O problema que o trait resolveria não existe no sítio + alegado — mas o trait ainda tem valor como abstração de composição + (adotado). +2. **Assinatura quebra o otimizador**: `fn parametros(&self) -> Vec<&Tensor>` + devolve visões somente-leitura; a partir de `&Tensor` não se recupera a + `Var` para `set()` in-place ⇒ qualquer otimizador via trait ficaria + **degradado a cópia-e-devolve** (ou não compila). Corrigido para + `Vec`. +3. **Violação de segregação de interface**: `gate_ativacao` na base força + TODA camada a ter gate de punição. Movida para o trait separado + `CamadaHomeostatica`. +4. **"Prova" mal tipada**: "a composição de funções lineares e ativações + preserva a dimensionalidade do batch" — falso para D (projeções mudam + D por definição). O invariante correto é sobre **B×T**, provado como + Teo CK-1 (em `camadas.rs`): reshape canônico fixa B·T; matmul age à + direita; ativações elemento-a-elemento. ∎ +5. **Casamento incompleto com a MoE**: `MoeRedirecionavel::forward(x, papel)` + é parametrizada por papel; forçar `forward(&self, x)` perderia o + parâmetro (proibido remover). Resolvido com o **adaptador zero-custo** + `MoeComPapel { moe, papel }` que captura o papel — os caminhos de + treino seguem usando o forward completo (com perda auxiliar e carga). + +### 3.2 Implementação (aditiva) + +`camadas.rs`: `trait CamadaKhtst { forward; parametros }`, +`trait CamadaHomeostatica { gate_ativacao(&Probabilidade) -> GateAtivacao }`, +impls para `Linear`, `MlpJepa`, `MoeComPapel`; `fnh.rs`: impl +`CamadaHomeostatica` para `GatePunicao` (via `valor_tipado`, T-V2). + +### 3.3 Testes (4 novos, todos verdes) + +- **ck1a**: composição heterogênea `Vec<&dyn CamadaKhtst>` = + Linear→MlpJepa sobre (2,3,8): saída (2,3,32) — B×T preservado (CK-1); + MoE via adaptador sobre (6,32). +- **ck1b**: `parametros()` não-vazio em todas as impls (otimizável). +- **ck1c**: adaptador delega **bit-a-bit** (comparação `to_bits()` por + elemento) para `moe.forward(x, papel).saida`. +- **ck2a**: despacho homeostático heterogêneo: zona morta exata em + π=0,2 < θ_π; gate > 0 em π=0,95. + +## §4 — Crítica 3 + Proposta 3: checkpoint atômico com prova de integridade + +### 4.1 Análise da crítica — VÁLIDA + +O padrão v0.9.0 era `fs::write(tmp)` + `rename` **sem fsync** e **sem +verificação de integridade**; o Python decidia resume por +`os.path.exists`. Janelas reais de falha: (a) queda de energia após o +rename mas antes do flush dos dados ⇒ arquivo final existe com conteúdo +zero/parcial (metadata journalizada, dados não — comportamento +dependente do FS); (b) TOCTOU entre `exists()` e o carregamento; +(c) corrupção silenciosa (bit rot, truncamento por ENOSPC parcial) +indetectável — o carregador legado só falha SE o JSON quebrar, e um +checkpoint truncado no meio de um array passa despercebido até o +`dados.len() != produto(forma)` — que existia, mas sem hash não detecta +substituição de mesmo formato. + +### 4.2 Erratas da proposta (3) + +1. **Destruição do estado anterior**: `File::create(caminho_pesos)` + trunca o checkpoint VÁLIDO anterior ANTES de escrever o novo. Queda + de energia no meio da escrita = perda dos DOIS estados. A atomicidade + real exige tmp+rename (a v0.9.0 JÁ fazia tmp+rename; a proposta seria + um REGRESSO). +2. **Manifesto sem barreira**: `fs::write(caminho_meta, ...)` sem + `sync_all` — o manifesto pode não atingir o disco quando os pesos já + atingiram, violando a própria ordenação que a "prova de atomicidade" + alegada depende. +3. **`bincode::serialize(...).unwrap()`** em caminho de I/O + dependência + nova para serializar `&[f32]` que o projeto já serializa em JSON; + crates.io inacessível no ambiente ⇒ dependência adicional + inaceitável. **SHA-256 implementado em Rust puro** (FIPS 180-4, + ~90 linhas) com os vetores oficiais como teste (cp0a). + +**Errata de linguagem**: "Se M existe e H(W) == M.hash, então W é +matematicamente idêntico ao salvo" — exagero; o correto é +**indistinguibilidade computacional** com P[colisão] ≤ 2⁻¹²⁸ (limite de +aniversário, CP-3). Truncamento é capturado DETERMINISTICAMENTE pelo +campo `tamanho`. + +### 4.3 O que foi implementado (Teos CP-1, CP-2, CP-3 em `checkpoint.rs`) + +Ordem de durabilidade: `write(W.tmp) → fsync(W.tmp) → rename(W) → +write(M.tmp) → fsync(M.tmp) → rename(M) → fsync(dir)`. Manifesto +lado-a-lado `{ck}.manifest.json` = `{versao, algoritmo, hash_sha256, +tamanho}`. API: `salvar_atomico`, `carregar_verificado` (Err se +manifesto ausente/divergente), `validar` (bool p/ Python), +`sha256_hex`. Integrado em `gerador.salvar`, `tokenizador.salvar`, +`Bpe.salvar` (python) — **os carregadores legados permanecem** +(compatibilidade v0.9.0; nada removido); acrescentados +`carregar_verificado` (gerador/tokenizador) e `carregar` nativo do +tokenizador (antes só existia no helper Python). + +**Custo**: manifesto de 140–141 B por checkpoint (overhead 5,8e-7 do +checkpoint de 240,9 MB); SHA-256 ≈ 1,1 s/240 MB em release single-thread +(medido dentro do salvar; amornado pelo próprio treino). + +### 4.4 Testes (6 novos + drill Python) + +cp0a (vetores FIPS: vazio, "abc", 3-blocos), cp1a (roundtrip + tmp +limpo), cp1b (**falha simulada** — dir somente-leitura ⇒ Err e o +checkpoint ANTERIOR permanece válido), cp3a (adulteração 1 byte), +cp3b (truncamento), cp4a (manifesto ausente recusado), cp5a (manifesto +de outro arquivo recusado). **Drill Python** (política de resume do CLI): +íntegro→"válido"; adulterado→"corrompido (apagado)"; legado→reuso com +detecção por parse. + +## §5 — Validação com dataset real (Wikipédia PT-BR) + +Pipeline A/B/C fresco na pilha v0.10.0 (1.121 sequências ctx=256, +`wikimedia/wikipedia 20231101.pt`, streaming controlado; o +`telemetria_fase_c.jsonl` residual da v0.9.0 foi apagado antes — §1): + +| fase | antes | depois | custo | observação | +|---|---|---|---|---| +| A (tokenizador GSQ+RCO) | 9,8818 | **8,7258** | 449 s | acc_recon 0,123; **δ_ort 1,01e-06** (ortogonalidade exata em treino real) | +| B (gerador NCM) | 4,1010 | **3,8219** | 266 s | abaixo do acaso ln 64 = 4,159 (aprendizado real) | +| C (conjunto, p/ FNH) | 3,7730 | **3,7508** | cap 400 s | δ_ort 2,0e-07; π(C)=0,000 ⇒ gate corretamente FECHADO (homeostase: punição saudável) | + +Checkpoints verificados pós-treino: tokenizador 240.853.123 B +(sha256 `202e9d9fbeba8551…`, válido), gerador 59.527.584 B +(`74a7a4595b1b4b20…`, válido). Resume subsequente via CLI: +"válido → reutilizando" (as fases A/B não retreinaram — prova +criptográfica de que o estado em disco é o salvo). + +**Leitura honesta**: as magnitudes de perda são comparáveis às da v0.9.0 +(CE C ≈ 3,75–3,93 dependendo do lote; confiança absoluta segue na casa +0,01–0,02, o débito de 10–50× compute das r-07/r-27/r-32 permanece). O +que a v0.10.0 PROVA é a **integridade e a reprodutibilidade do estado** +— o que a crítica 3 pedia — sem regressão de aprendizado. + +## §6 — Crítica 2 + Proposta 4: CLI unificado (MLOps) + +**Análise**: a pasta `scripts/` tem 36 arquivos numerados — factualmente +correto. A proposta "substituir por um CLI declarativo" foi **rejeitada +nos termos**: (a) os scripts numerados são a **trilha de rastreabilidade** +das memórias (r-04 cita "scripts 39-43", r-22 cita "48-54", r-27 "59", +r-32 "61-66", r-33 "68-69", r-34 "70-74"); (b) a regra da casa proíbe +remover funcionalidade eficaz; (c) o exemplo de código da proposta +referencia `K.Config`/`K.treinar_fase_a` (corretos por coincidência) mas +inventa `validar_integridade` sem fonte e fixa `fase 1|2|3` — o pipeline +real tem fases A/B/C com parâmetros distintos. + +**Implementação (aditiva)**: `scripts/75_cli.py` (Typer 0.23): +`versao`, `coletar`, `treinar` (com pré-validação SHA-256 dos +checkpoints — política §4.4), `verificar-checkpoints` (relatório), +`suite`, `telemetria`, `publicar` (exige `--confirmar` + `HF_TOKEN` no +ambiente), `estados` (varre/apaga estados locais). Delegação por +subprocess aos scripts canônicos — **zero duplicação de lógica**. + +## §7 — Autocrítica (minha) + +1. **O split quase reintroduziu o problema que criticava**: a 1ª + tentativa criou `python/{jepa, pensamento, verificador}.rs` — nomes + que SOMBREIAM `crate::{jepa, pensamento, verificador}` via + `use super::*` + `use crate::*`. Detectado pelos erros E0425/E0433, + corrigido com renomes (`jepa_treinos`, `pensamento_demos`, + `calibracao`). Lição: divisão por domínio exige auditoria de colisão + de nomes com a raiz do crate. +2. **Dois passos de edição quebrados meus** (stub vazio em + `carregar_de_txt`; substituição que apagou `carregar_verificado` + em vez de precedê-lo) — ambos capturados pelo compilador antes de + qualquer teste/publicação; o corpo original do `tokenizador::carregar` + foi recuperado do git (não existia — era helper Python; reimplementado + nativo como ADIÇÃO). +3. **O drill de corrupção falhou na 1ª execução por erro MEU** no cenário + (manifesto com `tamanho: 15` para 16 bytes) — a função agiu + CORRETAMENTE (tamanho divergente ⇒ corrompido). O "fracasso" do + teste era o teste funcionando; documentado aqui porque um leitor + apressado teria "corrigido" a função errada. +4. **fnh.rs (57,6 kB) agora é o maior arquivo** — próximo do menor + monólito criticado (50,9 kB). Não dividido nesta versão: suas + responsabilidades (rotações/cayley/suavizador/gate/camada) são + coesas sob o domínio FNH e a divisão adicionaria acoplamento + artificial; registrado como débito de observação. +5. **A medição incremental tem ruído da máquina** (±0,15 s) — por isso o + grupo de controle; a conclusão (refutação de O(N/k) em granularidade + de arquivo) é robusta ao ruído porque o EFEITO alegado seria de + ordens de magnitude, não de 15%. +6. **`versao()` estava fixada em "0.9.0"** — corrigida para derivar de + `CARGO_PKG_VERSION` (o build antigo mentia a própria versão; um + bug meu de versões anteriores que a telemetria v0.10.0 expôs). + +## §8 — Débitos honestos remanescentes + +- **Divisão em crates (workspace)** para o ganho O(N/k) real de + compilação: especificado em CK-0, não executado (custo de migração + PyO3/feature-unification alto; benefício atual pequeno — builds frios + de 68–86 s não justificam). +- **fsync de diretório é Linux-only** (`#[cfg(unix)]`); em plataformas + sem fsync de dir aberto, a durabilidade do rename fica a cargo do SO + (documentado no fonte). +- **SHA-256 single-thread** (~1,1 s/240 MB): paralelizável por blocos se + os checkpoints crescerem de ordem de magnitude. +- **Telemetria jsonl da fase C** continua append sem manifesto — é log, + não estado; o resume tolera linha parcial (parse por linha) mas não a + valida por hash (extensão: rotacionar com hash por segmento). +- **Kani** para os newtypes: mesmo débito justificado da r-34 §6. +- **Confiança absoluta 0,01–0,02** segue exigindo 10–50× compute + (herdado); a v0.10.0 garante a INTEGRIDADE do estado que se tem, não + a magnitude do aprendizado. + +## §9 — Rastreabilidade (afirmação → decisão → código) + +| prova | sítio | teste | +|---|---|---| +| CK-0 (granularidade) | r-35 §2.1/§2.4 | medições §2.3 (telemetria_v010.json.compilacao) | +| CK-1 (composição B×T) | camadas.rs (doc do trait) | ck1a | +| CP-1 (troca atômica) | checkpoint.rs | cp1a, cp1b | +| CP-2 (durabilidade ordenada) | checkpoint.rs | cp1a + ordem no código | +| CP-3 (detecção) | checkpoint.rs | cp0a, cp3a, cp3b, cp5a | +| T-V3 (γ ∈ (0,∞) por construção) | tipos.rs | tv3a/b/c | +| T-V3 fronteira verificador | verificador.rs | tv3d, tv3e | +| MoeComPapel bit-a-bit | camadas.rs | ck1c | +| política de resume | scripts/75_cli.py | drill §4.4 + treino real §5 | + +**Gates consolidados (script 77)**: 107/107 = 97 herdados v0.9.0 + 10 +novos (s1×3, s2×1[2 checkpoints], s3s4×1, s5×4, s6×1). Cargo 123/123 +(107 herdados + 16 novos). Determinismo preservado pela construção +aditiva (nenhum caminho existente alterado semanticamente; oráculos +legados intactos). diff --git a/pyproject.toml b/pyproject.toml index 2cee095da640d84db4b28e2a12584a5f6cec6a1f..fc274df8725e1dcb8444b140acb904551c0f77d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "maturin" [project] name = "khtst-rust" -version = "0.9.0" -description = "KHTST_RUST — NCM com verificador calibrado, MoEs redirecionáveis com forward canônico (gradiente íntegro), FNH com skew O(n²) e calibração vetorial, FFI Python isolada em feature e newtypes de domínio (críticas Qwen3.8 analisadas/provadas/testadas em r-34)" +version = "0.10.0" +description = "KHTST_RUST — v0.10.0: SRP nos módulos, checkpoints atômicos com prova criptográfica, tipos de domínio com invariantes, polimorfismo de camadas e CLI MLOps unificada (críticas Qwen3.8 lote 3 analisadas/provadas/testadas em r-35)" requires-python = ">=3.10" license = { text = "Apache-2.0" } diff --git a/resultados/fase_a_v010.json b/resultados/fase_a_v010.json new file mode 100644 index 0000000000000000000000000000000000000000..060223b6e8e0ddec8c5676d3142835d8d8c7f36e --- /dev/null +++ b/resultados/fase_a_v010.json @@ -0,0 +1,14 @@ +{ + "fase": "a", + "resumo": { + "acc_recon": 0.123046875, + "delta_ort_max": 1.012120244413075e-06, + "escore_aprendizado": 0.34142467641580315, + "monotonia_prefixo": 0.5, + "passos": 400, + "perda_final": 8.72581958770752, + "perda_inicial": 9.88181209564209, + "perplexidade_codebook": 54.26808964591084 + }, + "tempo_s": 449.4470684528351 +} \ No newline at end of file diff --git a/resultados/fase_b_v010.json b/resultados/fase_b_v010.json new file mode 100644 index 0000000000000000000000000000000000000000..1798e615744be41531c02bf235d397e99dd48447 --- /dev/null +++ b/resultados/fase_b_v010.json @@ -0,0 +1,11 @@ +{ + "fase": "b", + "resumo": { + "acc_conceitos": 0.10212053571428571, + "escore_aprendizado": 0.19593913663076906, + "passos": 400, + "perda_final": 3.8218880721500943, + "perda_inicial": 4.1010067122323175 + }, + "tempo_s": 265.64751839637756 +} \ No newline at end of file diff --git a/resultados/suite_v010_final.json b/resultados/suite_v010_final.json new file mode 100644 index 0000000000000000000000000000000000000000..47527aacb6fbe4427885fce91d9e4aa24a67614b --- /dev/null +++ b/resultados/suite_v010_final.json @@ -0,0 +1,159 @@ +{ + "versao": "0.10.0", + "blocos": { + "legado_v090": "97/97", + "s1_divisao_srp": "3/3", + "s2_checkpoint_atomico": "2/2", + "s3s4_tipos_trait": "1/1", + "s5_treino_real": "4/4", + "s6_cli": "1/1" + }, + "gates": { + "legado_legado_legado_legado_calibracao_soundness.v1_calibracao": true, + "legado_legado_legado_legado_calibracao_soundness.v2_soundness": true, + "legado_legado_legado_legado_calibracao_soundness.v3_tau_positivo": true, + "legado_legado_legado_legado_ciclo_d_sem_oraculo.aprendeu": true, + "legado_legado_legado_legado_ciclo_d_sem_oraculo.terminou": true, + "legado_legado_legado_legado_ciclo_d_sem_oraculo.sem_oraculo": true, + "legado_legado_legado_legado_ciclo_d_sem_oraculo.calibrado": true, + "legado_legado_legado_legado_ciclo_d_sem_oraculo.ganho_nao_negativo": true, + "legado_legado_legado_legado_ciclo_d_sem_oraculo.verificador_sem_oraculo": true, + "legado_legado_legado_legado_escalonamento_n.tp2_ncm": true, + "legado_legado_legado_legado_fase_c.verificador_sem_oraculo": true, + "legado_legado_legado_legado_fase_c.calibracao_nll_otima": true, + "legado_legado_legado_legado_fase_c.ece_aceitavel_para_soundness": true, + "legado_legado_legado_legado_fase_c.correcao_ativa": true, + "legado_legado_legado_legado_fase_c.acoplador_sem_timeout": true, + "legado_legado_legado_legado_fase_c.acoplador_duplex_ativo": true, + "legado_legado_legado_legado_fase_c.confianca_pos_nao_inferior": true, + "legado_legado_legado_legado_fase_c.backbone_preservado": true, + "legado_legado_legado_legado_fase_c.pcgrad_sem_conflito_observado": true, + "legado_legado_legado_legado_provas_mr.mr1_capacidade": true, + "legado_legado_legado_legado_provas_mr.mr1_perda_aux": true, + "legado_legado_legado_legado_provas_mr.mr2_neutralidade": true, + "legado_legado_legado_legado_provas_mr.mr3_paralelismo": true, + "legado_legado_legado_legado_provas_mr.mr5_water_filling": true, + "legado_legado_legado_legado_jepa_moe.jepa_moe_aprende": true, + "legado_legado_legado_legado_jepa_moe.jepa_moe_vs_densa": true, + "legado_legado_legado_legado_jepa_moe.jepa_moe_vs_pequena": true, + "legado_legado_legado_legado_som_moe.moe_refina": true, + "legado_legado_legado_legado_som_moe.redirect_decodifica": true, + "legado_legado_legado_legado_som_moe.wf_melhor_uniforme": true, + "legado_legado_legado_legado_acoplador_duplo.duplex_simultaneo": true, + "legado_legado_legado_legado_acoplador_duplo.sem_timeout": true, + "legado_legado_legado_legado_acoplador_duplo.terminou": true, + "legado_legado_legado_legado_iter.gate_ucb1_encontrou_moe": true, + "legado_legado_legado_legado_iter.gate_gap_positivo_vs_baseline": true, + "legado_legado_legado_legado_iter.gate_arrependimento_pequeno": true, + "legado_legado_legado_legado_iter.gate_regulacao_ocorreu": true, + "legado_legado_legado_legado_iter.gate_parada_ou_teto": true, + "legado_legado_legado_legado_iter.gate_melhor_aprendeu": true, + "legado_legado_legado_legado_iter.a1_tau_quantil": true, + "legado_legado_legado_legado_iter.a2_b_finito": true, + "legado_legado_legado_legado_iter.a3_n_reduzido": true, + "legado_legado_legado_legado_iter.a4_ucb1_achou": true, + "legado_legado_legado_legado_iter.a5_gate_subiu": true, + "legado_legado_legado_legado_legada_suite_v": true, + "legado_legado_legado_legado_cargo_54": true, + "legado_legado_legado_fnh_f1_so_n_exato": true, + "legado_legado_legado_fnh_f3_gate_zona_morta": true, + "legado_legado_legado_fnh_f3_gate_abre": true, + "legado_legado_legado_fnh_f4_homeostase_theta_congelado": true, + "legado_legado_legado_fnh_f4_rotacao_aprende_com_punicao": true, + "legado_legado_legado_fnh_a2_delta_ort_treino_real": true, + "legado_legado_legado_fnh_a3_punicao_real_medida": true, + "legado_legado_legado_arvore_quebra_t1_invariantes": true, + "legado_legado_legado_arvore_quebra_t2_agregacao_exata": true, + "legado_legado_legado_arvore_quebra_t3_teto_simetrico": true, + "legado_legado_legado_arvore_quebra_q1_executa_real": true, + "legado_legado_legado_arvore_quebra_q2_soundness_aceites": true, + "legado_legado_legado_arvore_quebra_q3_invariantes_pos_quebra": true, + "legado_legado_legado_arvore_quebra_q4_punicao_registrada": true, + "legado_legado_legado_arvore_quebra_q5_sintetico_mistura_aceita_rejeita": true, + "legado_legado_legado_nlp_nlg_n_coerencia_ok": true, + "legado_legado_legado_nlp_nlg_n_espec_ok": true, + "legado_legado_legado_nlp_nlg_n_gate_medio_ok": true, + "legado_legado_legado_nlp_nlg_n_nlg_neutro": true, + "legado_legado_legado_nlp_nlg_n_nlp_neutro": true, + "legado_legado_legado_nlp_nlg_n_ctx_real_bigramas_unicos": true, + "legado_legado_legado_nlp_nlg_s_piso_ok": true, + "legado_legado_legado_nlp_nlg_s_qe_sobe": true, + "legado_legado_legado_nlp_nlg_s_stop_dispara": true, + "legado_legado_legado_nlp_nlg_s_tetos_ok": true, + "legado_legado_legado_telemetria_painel_v070": true, + "legado_legado_legado_treino_real_completo": true, + "legado_legado_q1_newton_menos_avaliacoes": true, + "legado_legado_q1_newton_mesmo_t": true, + "legado_legado_q1_newton_nll_nao_piora": true, + "legado_legado_q3_cayley_ambos_exatos": true, + "legado_legado_q3_cayley_cayley_mais_rapido": true, + "legado_legado_q2_arvore_iguais_bit_a_bit": true, + "legado_legado_q4_moe_bug_v080_1_confirmado": true, + "legado_legado_q4_moe_gradiente_restaurado": true, + "legado_legado_q4_moe_valores_iguais": true, + "legado_legado_q5_ablacao_moe_aprende_3x": true, + "legado_legado_q5_ablacao_moe_domina_pequena_3x": true, + "legado_legado_cargo_todos_verdes": true, + "legado_q1_skew_bits_identicos": true, + "legado_q1_skew_mais_rapido_n128": true, + "legado_q2_calibrar_bit_a_bit": true, + "legado_q2_calibrar_nao_regride": true, + "legado_q3_forward_valores_e_gradiente": true, + "legado_q4_newtypes_fronteira_validada": true, + "legado_real_gate_abre_com_punicao_alta": true, + "legado_real_gate_fecha_com_punicao_baixa": true, + "legado_real_rotacao_aprende": true, + "legado_real_so_n_exato": true, + "legado_real_ce_decresce": true, + "legado_q5_compila_sem_python": true, + "s1_nenhum_arquivo_acima_60kB": true, + "s1_monolitos_dissolvidos": true, + "s1_testes_herdados_preservados": true, + "s2_checkpoints_sha256_validos": true, + "s3s4_testes_novos_16": true, + "s5a_faseA_perda_cai": true, + "s5b_faseB_perda_cai": true, + "s5c_faseC_ce_cai": true, + "s5d_ortogonalidade_exata": true, + "s6_cli_comandos": true + }, + "medicoes": { + "maior_arquivo": { + "caminho": "src/fnh.rs", + "kB": 57.6 + }, + "cargo_total": 123, + "checkpoints": { + "tokenizador_conceitos.json": { + "valido": true, + "manifesto_bytes": 141 + }, + "gerador_conceitos.json": { + "valido": true, + "manifesto_bytes": 140 + } + }, + "testes_novos_v010": 16, + "treino_real": { + "a": { + "perda_inicial": 9.88181209564209, + "perda_final": 8.72581958770752, + "delta_ort_max": 1.012120244413075e-06 + }, + "b": { + "perda_inicial": 4.1010067122323175, + "perda_final": 3.8218880721500943 + }, + "c": { + "ce_inicial": 3.773000853402274, + "ce_final": 3.750825984137399, + "taxa_gatilho": null + } + }, + "cli": { + "versao": "khtst-rust-0.10.0", + "checkpoints_relacionados": 2 + } + }, + "veredito": "107/107 — APROVADO" +} \ No newline at end of file diff --git a/resultados/telemetria_fase_c.jsonl b/resultados/telemetria_fase_c.jsonl index c0f9c5aa7a1283f6d262ff1dcb7551e7fe88df37..8256178dabba3dc8dc3ea9f8dea4ac84c6f57637 100644 --- a/resultados/telemetria_fase_c.jsonl +++ b/resultados/telemetria_fase_c.jsonl @@ -1,150 +1,168 @@ {"tipo":"ajuste","dados":{"passo":0,"parametro":"fase_c_base","de":0.0,"para":1.0,"motivo":"carregado checkpoint da fase B: /home/z/my-project/khtst-rust/resultados/gerador_conceitos.json (ajuste fino, não do zero)"}} -{"tipo":"ajuste","dados":{"passo":0,"parametro":"pensamento_tau_c","de":0.3,"para":0.015944182872772217,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, calibração inicial fase C)"}} -{"tipo":"ajuste","dados":{"passo":0,"parametro":"pensamento_tau_c","de":0.015944182872772217,"para":0.014759302139282227,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=1024)"}} -{"tipo":"ajuste","dados":{"passo":1,"parametro":"pensamento_tau_c","de":0.014759302139282227,"para":0.015311956405639648,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=2048)"}} -{"tipo":"ajuste","dados":{"passo":2,"parametro":"pensamento_tau_c","de":0.015311956405639648,"para":0.015118539333343506,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=3072)"}} -{"tipo":"ajuste","dados":{"passo":3,"parametro":"pensamento_tau_c","de":0.015118539333343506,"para":0.015353560447692871,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=4096)"}} -{"tipo":"ajuste","dados":{"passo":4,"parametro":"pensamento_tau_c","de":0.015353560447692871,"para":0.015562117099761963,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=5120)"}} -{"tipo":"ajuste","dados":{"passo":5,"parametro":"pensamento_tau_c","de":0.015562117099761963,"para":0.0155600905418396,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=6144)"}} -{"tipo":"ajuste","dados":{"passo":6,"parametro":"pensamento_tau_c","de":0.0155600905418396,"para":0.0156288743019104,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=7168)"}} -{"tipo":"ajuste","dados":{"passo":7,"parametro":"pensamento_tau_c","de":0.0156288743019104,"para":0.015540003776550293,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=8192)"}} -{"tipo":"ajuste","dados":{"passo":8,"parametro":"pensamento_tau_c","de":0.015540003776550293,"para":0.015531718730926514,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=9216)"}} -{"tipo":"ajuste","dados":{"passo":9,"parametro":"pensamento_tau_c","de":0.015531718730926514,"para":0.01559746265411377,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=10240)"}} -{"tipo":"ajuste","dados":{"passo":10,"parametro":"pensamento_tau_c","de":0.01559746265411377,"para":0.015570282936096191,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=11264)"}} -{"tipo":"ajuste","dados":{"passo":11,"parametro":"pensamento_tau_c","de":0.015570282936096191,"para":0.015507221221923828,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=12288)"}} -{"tipo":"ajuste","dados":{"passo":12,"parametro":"pensamento_tau_c","de":0.015507221221923828,"para":0.01548987627029419,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=13312)"}} -{"tipo":"ajuste","dados":{"passo":13,"parametro":"pensamento_tau_c","de":0.01548987627029419,"para":0.015411674976348877,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=14336)"}} -{"tipo":"ajuste","dados":{"passo":14,"parametro":"pensamento_tau_c","de":0.015411674976348877,"para":0.015387296676635742,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=15360)"}} -{"tipo":"ajuste","dados":{"passo":15,"parametro":"pensamento_tau_c","de":0.015387296676635742,"para":0.015422582626342773,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=16384)"}} -{"tipo":"ajuste","dados":{"passo":15,"parametro":"pensamento_n_tentativas","de":4.0,"para":8.0,"motivo":"A-3 Hoeffding+acordo (â=0.513 ⇒ p̂=0.581, δ=0.05)"}} -{"tipo":"ajuste","dados":{"passo":16,"parametro":"pensamento_tau_c","de":0.015422582626342773,"para":0.015360474586486816,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=17408)"}} -{"tipo":"ajuste","dados":{"passo":17,"parametro":"pensamento_tau_c","de":0.015360474586486816,"para":0.015303552150726318,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=18432)"}} -{"tipo":"ajuste","dados":{"passo":18,"parametro":"pensamento_tau_c","de":0.015303552150726318,"para":0.015320897102355957,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=19456)"}} -{"tipo":"ajuste","dados":{"passo":19,"parametro":"pensamento_tau_c","de":0.015320897102355957,"para":0.015316307544708252,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=20480)"}} -{"tipo":"ajuste","dados":{"passo":20,"parametro":"pensamento_tau_c","de":0.015316307544708252,"para":0.015308499336242676,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=21504)"}} -{"tipo":"ajuste","dados":{"passo":21,"parametro":"pensamento_tau_c","de":0.015308499336242676,"para":0.015275835990905762,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=22528)"}} -{"tipo":"ajuste","dados":{"passo":22,"parametro":"pensamento_tau_c","de":0.015275835990905762,"para":0.01528865098953247,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=23552)"}} -{"tipo":"ajuste","dados":{"passo":23,"parametro":"pensamento_tau_c","de":0.01528865098953247,"para":0.015301764011383057,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=24576)"}} -{"tipo":"ajuste","dados":{"passo":24,"parametro":"pensamento_tau_c","de":0.015301764011383057,"para":0.015325963497161865,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=25600)"}} -{"tipo":"ajuste","dados":{"passo":25,"parametro":"pensamento_tau_c","de":0.015325963497161865,"para":0.015384972095489502,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=26624)"}} -{"tipo":"ajuste","dados":{"passo":26,"parametro":"pensamento_tau_c","de":0.015384972095489502,"para":0.015403449535369873,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=27648)"}} -{"tipo":"ajuste","dados":{"passo":27,"parametro":"pensamento_tau_c","de":0.015403449535369873,"para":0.01539081335067749,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=28672)"}} -{"tipo":"ajuste","dados":{"passo":28,"parametro":"pensamento_tau_c","de":0.01539081335067749,"para":0.015397608280181885,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=29696)"}} -{"tipo":"ajuste","dados":{"passo":29,"parametro":"pensamento_tau_c","de":0.015397608280181885,"para":0.015385568141937256,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=30720)"}} -{"tipo":"ajuste","dados":{"passo":30,"parametro":"pensamento_tau_c","de":0.015385568141937256,"para":0.015361487865447998,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=31744)"}} -{"tipo":"ajuste","dados":{"passo":31,"parametro":"pensamento_tau_c","de":0.015361487865447998,"para":0.015375494956970215,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=32768)"}} -{"tipo":"ajuste","dados":{"passo":32,"parametro":"pensamento_tau_c","de":0.015375494956970215,"para":0.015370070934295654,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=33792)"}} -{"tipo":"ajuste","dados":{"passo":33,"parametro":"pensamento_tau_c","de":0.015370070934295654,"para":0.01535642147064209,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=34816)"}} -{"tipo":"ajuste","dados":{"passo":34,"parametro":"pensamento_tau_c","de":0.01535642147064209,"para":0.015362977981567383,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=35840)"}} -{"tipo":"ajuste","dados":{"passo":35,"parametro":"pensamento_tau_c","de":0.015362977981567383,"para":0.015390396118164062,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=36864)"}} -{"tipo":"ajuste","dados":{"passo":36,"parametro":"pensamento_tau_c","de":0.015390396118164062,"para":0.015380859375,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=37888)"}} -{"tipo":"ajuste","dados":{"passo":37,"parametro":"pensamento_tau_c","de":0.015380859375,"para":0.015377163887023926,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=38912)"}} -{"tipo":"ajuste","dados":{"passo":38,"parametro":"pensamento_tau_c","de":0.015377163887023926,"para":0.015380620956420898,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=39936)"}} -{"tipo":"ajuste","dados":{"passo":39,"parametro":"pensamento_tau_c","de":0.015380620956420898,"para":0.015387535095214844,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=40960)"}} -{"tipo":"ajuste","dados":{"passo":40,"parametro":"pensamento_tau_c","de":0.015387535095214844,"para":0.015380859375,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=41984)"}} -{"tipo":"ajuste","dados":{"passo":41,"parametro":"pensamento_tau_c","de":0.015380859375,"para":0.015370666980743408,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=43008)"}} -{"tipo":"ajuste","dados":{"passo":42,"parametro":"pensamento_tau_c","de":0.015370666980743408,"para":0.015360474586486816,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=44032)"}} -{"tipo":"ajuste","dados":{"passo":43,"parametro":"pensamento_tau_c","de":0.015360474586486816,"para":0.015366554260253906,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=45056)"}} -{"tipo":"ajuste","dados":{"passo":44,"parametro":"pensamento_tau_c","de":0.015366554260253906,"para":0.015380859375,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=46080)"}} -{"tipo":"ajuste","dados":{"passo":45,"parametro":"pensamento_tau_c","de":0.015380859375,"para":0.015369832515716553,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=47104)"}} -{"tipo":"ajuste","dados":{"passo":47,"parametro":"pensamento_tau_c","de":0.015369832515716553,"para":0.015374183654785156,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=48128)"}} -{"tipo":"ajuste","dados":{"passo":48,"parametro":"pensamento_tau_c","de":0.015374183654785156,"para":0.015362024307250977,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=49152)"}} -{"tipo":"ajuste","dados":{"passo":49,"parametro":"pensamento_tau_c","de":0.015362024307250977,"para":0.015375256538391113,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=50176)"}} -{"tipo":"ajuste","dados":{"passo":50,"parametro":"pensamento_tau_c","de":0.015375256538391113,"para":0.01537179946899414,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=51200)"}} -{"tipo":"ajuste","dados":{"passo":51,"parametro":"pensamento_tau_c","de":0.01537179946899414,"para":0.015378892421722412,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=52224)"}} -{"tipo":"ajuste","dados":{"passo":52,"parametro":"pensamento_tau_c","de":0.015378892421722412,"para":0.01537775993347168,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=53248)"}} -{"tipo":"ajuste","dados":{"passo":53,"parametro":"pensamento_tau_c","de":0.01537775993347168,"para":0.015381455421447754,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=54272)"}} -{"tipo":"ajuste","dados":{"passo":54,"parametro":"pensamento_tau_c","de":0.015381455421447754,"para":0.015369951725006104,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=55296)"}} -{"tipo":"ajuste","dados":{"passo":55,"parametro":"pensamento_tau_c","de":0.015369951725006104,"para":0.015365362167358398,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=56320)"}} -{"tipo":"ajuste","dados":{"passo":56,"parametro":"pensamento_tau_c","de":0.015365362167358398,"para":0.015367567539215088,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=57344)"}} -{"tipo":"ajuste","dados":{"passo":58,"parametro":"pensamento_tau_c","de":0.015367567539215088,"para":0.015363991260528564,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=59392)"}} -{"tipo":"ajuste","dados":{"passo":59,"parametro":"pensamento_tau_c","de":0.015363991260528564,"para":0.015357553958892822,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=60416)"}} -{"tipo":"ajuste","dados":{"passo":60,"parametro":"pensamento_tau_c","de":0.015357553958892822,"para":0.0153464674949646,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=61440)"}} -{"tipo":"ajuste","dados":{"passo":61,"parametro":"pensamento_tau_c","de":0.0153464674949646,"para":0.01534128189086914,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=62464)"}} -{"tipo":"ajuste","dados":{"passo":62,"parametro":"pensamento_tau_c","de":0.01534128189086914,"para":0.015345454216003418,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=63488)"}} -{"tipo":"ajuste","dados":{"passo":63,"parametro":"pensamento_tau_c","de":0.015345454216003418,"para":0.01535332202911377,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=64512)"}} -{"tipo":"ajuste","dados":{"passo":65,"parametro":"pensamento_tau_c","de":0.01535332202911377,"para":0.015338540077209473,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=66560)"}} -{"tipo":"ajuste","dados":{"passo":66,"parametro":"pensamento_tau_c","de":0.015338540077209473,"para":0.015347063541412354,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=67584)"}} -{"tipo":"ajuste","dados":{"passo":67,"parametro":"pensamento_tau_c","de":0.015347063541412354,"para":0.015357673168182373,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=68608)"}} -{"tipo":"ajuste","dados":{"passo":69,"parametro":"pensamento_tau_c","de":0.015357673168182373,"para":0.015349328517913818,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=69632)"}} -{"tipo":"ajuste","dados":{"passo":70,"parametro":"pensamento_tau_c","de":0.015349328517913818,"para":0.015347599983215332,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=70656)"}} -{"tipo":"ajuste","dados":{"passo":71,"parametro":"pensamento_tau_c","de":0.015347599983215332,"para":0.015345633029937744,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=71680)"}} -{"tipo":"ajuste","dados":{"passo":72,"parametro":"pensamento_tau_c","de":0.015345633029937744,"para":0.015339553356170654,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=72704)"}} -{"tipo":"ajuste","dados":{"passo":73,"parametro":"pensamento_tau_c","de":0.015339553356170654,"para":0.01534736156463623,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=73728)"}} -{"tipo":"ajuste","dados":{"passo":74,"parametro":"pensamento_tau_c","de":0.01534736156463623,"para":0.015366971492767334,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=74752)"}} -{"tipo":"ajuste","dados":{"passo":75,"parametro":"pensamento_tau_c","de":0.015366971492767334,"para":0.015371441841125488,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=75776)"}} -{"tipo":"ajuste","dados":{"passo":76,"parametro":"pensamento_tau_c","de":0.015371441841125488,"para":0.015366733074188232,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=76800)"}} -{"tipo":"ajuste","dados":{"passo":77,"parametro":"pensamento_tau_c","de":0.015366733074188232,"para":0.015358150005340576,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=77824)"}} -{"tipo":"ajuste","dados":{"passo":79,"parametro":"pensamento_tau_c","de":0.015358150005340576,"para":0.015360116958618164,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=78848)"}} -{"tipo":"ajuste","dados":{"passo":80,"parametro":"pensamento_tau_c","de":0.015360116958618164,"para":0.015347719192504883,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=79872)"}} -{"tipo":"ajuste","dados":{"passo":82,"parametro":"pensamento_tau_c","de":0.015347719192504883,"para":0.015350818634033203,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=80896)"}} -{"tipo":"ajuste","dados":{"passo":83,"parametro":"pensamento_tau_c","de":0.015350818634033203,"para":0.015345513820648193,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=81920)"}} -{"tipo":"ajuste","dados":{"passo":84,"parametro":"pensamento_tau_c","de":0.015345513820648193,"para":0.015348553657531738,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=82944)"}} -{"tipo":"ajuste","dados":{"passo":85,"parametro":"pensamento_tau_c","de":0.015348553657531738,"para":0.015350103378295898,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=83968)"}} -{"tipo":"ajuste","dados":{"passo":86,"parametro":"pensamento_tau_c","de":0.015350103378295898,"para":0.015343248844146729,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=84992)"}} -{"tipo":"ajuste","dados":{"passo":87,"parametro":"pensamento_tau_c","de":0.015343248844146729,"para":0.015337526798248291,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=86016)"}} -{"tipo":"ajuste","dados":{"passo":88,"parametro":"pensamento_tau_c","de":0.015337526798248291,"para":0.01534116268157959,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=87040)"}} -{"tipo":"ajuste","dados":{"passo":89,"parametro":"pensamento_tau_c","de":0.01534116268157959,"para":0.015345096588134766,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=88064)"}} -{"tipo":"ajuste","dados":{"passo":91,"parametro":"pensamento_tau_c","de":0.015345096588134766,"para":0.015353918075561523,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=90112)"}} -{"tipo":"ajuste","dados":{"passo":97,"parametro":"pensamento_tau_c","de":0.015353918075561523,"para":0.015366077423095703,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=94208)"}} -{"tipo":"ajuste","dados":{"passo":98,"parametro":"pensamento_tau_c","de":0.015366077423095703,"para":0.015379607677459717,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=95232)"}} -{"tipo":"ajuste","dados":{"passo":99,"parametro":"pensamento_tau_c","de":0.015379607677459717,"para":0.015378355979919434,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=96256)"}} -{"tipo":"ajuste","dados":{"passo":100,"parametro":"pensamento_tau_c","de":0.015378355979919434,"para":0.015393555164337158,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=97280)"}} -{"tipo":"ajuste","dados":{"passo":101,"parametro":"pensamento_tau_c","de":0.015393555164337158,"para":0.015401959419250488,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=98304)"}} -{"tipo":"ajuste","dados":{"passo":102,"parametro":"pensamento_tau_c","de":0.015401959419250488,"para":0.015395641326904297,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=99328)"}} -{"tipo":"ajuste","dados":{"passo":103,"parametro":"pensamento_tau_c","de":0.015395641326904297,"para":0.015393495559692383,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=100352)"}} -{"tipo":"ajuste","dados":{"passo":104,"parametro":"pensamento_tau_c","de":0.015393495559692383,"para":0.0153999924659729,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=101376)"}} -{"tipo":"ajuste","dados":{"passo":105,"parametro":"pensamento_tau_c","de":0.0153999924659729,"para":0.015404939651489258,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=102400)"}} -{"tipo":"ajuste","dados":{"passo":107,"parametro":"pensamento_tau_c","de":0.015404939651489258,"para":0.015412747859954834,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=103424)"}} -{"tipo":"ajuste","dados":{"passo":109,"parametro":"pensamento_tau_c","de":0.015412747859954834,"para":0.015414237976074219,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=105472)"}} -{"tipo":"ajuste","dados":{"passo":110,"parametro":"pensamento_tau_c","de":0.015414237976074219,"para":0.015423834323883057,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=106496)"}} -{"tipo":"ajuste","dados":{"passo":111,"parametro":"pensamento_tau_c","de":0.015423834323883057,"para":0.015425324440002441,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=107520)"}} -{"tipo":"ajuste","dados":{"passo":112,"parametro":"pensamento_tau_c","de":0.015425324440002441,"para":0.015429675579071045,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=108544)"}} -{"tipo":"ajuste","dados":{"passo":113,"parametro":"pensamento_tau_c","de":0.015429675579071045,"para":0.015437602996826172,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=109568)"}} -{"tipo":"ajuste","dados":{"passo":114,"parametro":"pensamento_tau_c","de":0.015437602996826172,"para":0.015431880950927734,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=110592)"}} -{"tipo":"ajuste","dados":{"passo":115,"parametro":"pensamento_tau_c","de":0.015431880950927734,"para":0.015434741973876953,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=111616)"}} -{"tipo":"ajuste","dados":{"passo":116,"parametro":"pensamento_tau_c","de":0.015434741973876953,"para":0.015452265739440918,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=112640)"}} -{"tipo":"ajuste","dados":{"passo":117,"parametro":"pensamento_tau_c","de":0.015452265739440918,"para":0.015465497970581055,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=113664)"}} -{"tipo":"ajuste","dados":{"passo":118,"parametro":"pensamento_tau_c","de":0.015465497970581055,"para":0.01547163724899292,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=114688)"}} -{"tipo":"ajuste","dados":{"passo":119,"parametro":"pensamento_tau_c","de":0.01547163724899292,"para":0.015480279922485352,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=115712)"}} -{"tipo":"ajuste","dados":{"passo":121,"parametro":"pensamento_tau_c","de":0.015480279922485352,"para":0.015484035015106201,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=117760)"}} -{"tipo":"ajuste","dados":{"passo":123,"parametro":"pensamento_tau_c","de":0.015484035015106201,"para":0.015488862991333008,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=118784)"}} -{"tipo":"ajuste","dados":{"passo":124,"parametro":"pensamento_tau_c","de":0.015488862991333008,"para":0.015490233898162842,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=119808)"}} -{"tipo":"ajuste","dados":{"passo":125,"parametro":"pensamento_tau_c","de":0.015490233898162842,"para":0.015501022338867188,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=120832)"}} -{"tipo":"ajuste","dados":{"passo":126,"parametro":"pensamento_tau_c","de":0.015501022338867188,"para":0.015498936176300049,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=121856)"}} -{"tipo":"ajuste","dados":{"passo":127,"parametro":"pensamento_tau_c","de":0.015498936176300049,"para":0.015510857105255127,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=122880)"}} -{"tipo":"ajuste","dados":{"passo":128,"parametro":"pensamento_tau_c","de":0.015510857105255127,"para":0.015512466430664062,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=123904)"}} -{"tipo":"ajuste","dados":{"passo":129,"parametro":"pensamento_tau_c","de":0.015512466430664062,"para":0.01551520824432373,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=124928)"}} -{"tipo":"ajuste","dados":{"passo":131,"parametro":"pensamento_tau_c","de":0.01551520824432373,"para":0.015510737895965576,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=125952)"}} -{"tipo":"ajuste","dados":{"passo":132,"parametro":"pensamento_tau_c","de":0.015510737895965576,"para":0.015512049198150635,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=126976)"}} -{"tipo":"ajuste","dados":{"passo":133,"parametro":"pensamento_tau_c","de":0.015512049198150635,"para":0.015520155429840088,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=128000)"}} -{"tipo":"ajuste","dados":{"passo":134,"parametro":"pensamento_tau_c","de":0.015520155429840088,"para":0.01553279161453247,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=129024)"}} -{"tipo":"ajuste","dados":{"passo":135,"parametro":"pensamento_tau_c","de":0.01553279161453247,"para":0.015529215335845947,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=130048)"}} -{"tipo":"ajuste","dados":{"passo":137,"parametro":"pensamento_tau_c","de":0.015529215335845947,"para":0.015536665916442871,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=131072)"}} -{"tipo":"ajuste","dados":{"passo":139,"parametro":"pensamento_tau_c","de":0.015536665916442871,"para":0.015539109706878662,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=132096)"}} -{"tipo":"ajuste","dados":{"passo":140,"parametro":"pensamento_tau_c","de":0.015539109706878662,"para":0.015540242195129395,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=133120)"}} -{"tipo":"ajuste","dados":{"passo":141,"parametro":"pensamento_tau_c","de":0.015540242195129395,"para":0.015550673007965088,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=134144)"}} -{"tipo":"ajuste","dados":{"passo":142,"parametro":"pensamento_tau_c","de":0.015550673007965088,"para":0.015557527542114258,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=135168)"}} -{"tipo":"ajuste","dados":{"passo":143,"parametro":"pensamento_tau_c","de":0.015557527542114258,"para":0.015567600727081299,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=136192)"}} -{"tipo":"ajuste","dados":{"passo":144,"parametro":"pensamento_tau_c","de":0.015567600727081299,"para":0.015576601028442383,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=137216)"}} -{"tipo":"ajuste","dados":{"passo":145,"parametro":"pensamento_tau_c","de":0.015576601028442383,"para":0.015579462051391602,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=138240)"}} -{"tipo":"ajuste","dados":{"passo":146,"parametro":"pensamento_tau_c","de":0.015579462051391602,"para":0.015575289726257324,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=139264)"}} -{"tipo":"ajuste","dados":{"passo":147,"parametro":"pensamento_tau_c","de":0.015575289726257324,"para":0.015587151050567627,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=140288)"}} -{"tipo":"ajuste","dados":{"passo":148,"parametro":"pensamento_tau_c","de":0.015587151050567627,"para":0.015595018863677979,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=141312)"}} -{"tipo":"ajuste","dados":{"passo":150,"parametro":"pensamento_tau_c","de":0.015595018863677979,"para":0.015592873096466064,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=142336)"}} -{"tipo":"ajuste","dados":{"passo":151,"parametro":"pensamento_tau_c","de":0.015592873096466064,"para":0.015603184700012207,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=143360)"}} -{"tipo":"ajuste","dados":{"passo":152,"parametro":"pensamento_tau_c","de":0.015603184700012207,"para":0.015616238117218018,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=144384)"}} -{"tipo":"ajuste","dados":{"passo":154,"parametro":"pensamento_tau_c","de":0.015616238117218018,"para":0.01562345027923584,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=146432)"}} -{"tipo":"ajuste","dados":{"passo":155,"parametro":"pensamento_tau_c","de":0.01562345027923584,"para":0.0156286358833313,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=147456)"}} -{"tipo":"ajuste","dados":{"passo":156,"parametro":"pensamento_tau_c","de":0.0156286358833313,"para":0.01563173532485962,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=148480)"}} -{"tipo":"ajuste","dados":{"passo":157,"parametro":"pensamento_tau_c","de":0.01563173532485962,"para":0.01563364267349243,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=149504)"}} -{"tipo":"ajuste","dados":{"passo":159,"parametro":"pensamento_tau_c","de":0.01563364267349243,"para":0.015644311904907227,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=150528)"}} -{"tipo":"ajuste","dados":{"passo":160,"parametro":"pensamento_tau_c","de":0.015644311904907227,"para":0.015646517276763916,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=151552)"}} -{"tipo":"ajuste","dados":{"passo":161,"parametro":"pensamento_tau_c","de":0.015646517276763916,"para":0.015659451484680176,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=152576)"}} -{"tipo":"ajuste","dados":{"passo":162,"parametro":"pensamento_tau_c","de":0.015659451484680176,"para":0.015669584274291992,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=153600)"}} -{"tipo":"ajuste","dados":{"passo":163,"parametro":"pensamento_tau_c","de":0.015669584274291992,"para":0.015673577785491943,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=154624)"}} -{"tipo":"ajuste","dados":{"passo":164,"parametro":"pensamento_tau_c","de":0.015673577785491943,"para":0.015671849250793457,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=155648)"}} -{"tipo":"ajuste","dados":{"passo":165,"parametro":"pensamento_tau_c","de":0.015671849250793457,"para":0.015684127807617188,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=156672)"}} -{"tipo":"ajuste","dados":{"passo":166,"parametro":"pensamento_tau_c","de":0.015684127807617188,"para":0.015687108039855957,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=157696)"}} -{"tipo":"ajuste","dados":{"passo":168,"parametro":"pensamento_tau_c","de":0.015687108039855957,"para":0.015690743923187256,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=158720)"}} -{"tipo":"dump","dados":{"acordo_tentativas":[[0,0.5026177413431773],[1,0.5188394377037184],[2,0.5132680885520434],[3,0.5208795444729427],[4,0.5188903896390306],[5,0.5011314397173313],[6,0.513138426564789],[7,0.5190980362628276],[8,0.5175182593617743],[9,0.5112785832598092],[10,0.5085292130922122],[11,0.5033831575419754],[12,0.5180072920726767],[13,0.5116050054396813],[14,0.5176283980681546],[15,0.5083806542800934],[16,0.5133745392959099],[17,0.5255541773415947],[18,0.5147984851306925],[19,0.5255529606753649],[20,0.5145055030394966],[21,0.5128630660183262],[22,0.5123117877131639],[23,0.5270465893748527],[24,0.5362580924411304],[25,0.5361006099071043],[26,0.5302464213091298],[27,0.5292475567451523],[28,0.5248556715863136],[29,0.5302380567688184],[30,0.5237250247155316],[31,0.5292592578431746],[32,0.52142780541908],[33,0.5059490310571467],[34,0.5189573994139209],[35,0.5229016945231706],[36,0.5221814555685947],[37,0.5259512477787212],[38,0.5287726925744209],[39,0.5010473615839146],[40,0.5031298764273137],[41,0.5167082861686746],[42,0.5111356303677894],[43,0.5363262977116392],[44,0.5328653223502139],[45,0.5415598337616151],[47,0.5225419494672678],[48,0.5240338932392964],[49,0.5192141458058662],[50,0.5229304549405546],[51,0.5202941066721299],[52,0.5231473246773627],[53,0.5283143232591101],[54,0.5103455298036957],[55,0.5281538160828253],[56,0.5077444059813084],[57,0.5209305513805399],[58,0.5138173264373714],[59,0.5303305063668328],[60,0.530609179326954],[61,0.5119197097277114],[62,0.507813933577078],[63,0.5079189598864104],[64,0.5304057532242344],[65,0.5238011309750922],[66,0.5227580087569853],[67,0.5190927191870287],[69,0.5125796055896595],[70,0.5227258977441428],[71,0.5449074280719893],[72,0.5317959284681516],[73,0.5351348237018101],[74,0.531177563573389],[75,0.5142779611439133],[76,0.5384028241921138],[77,0.5221474913135656],[79,0.5332707390577222],[80,0.5091511811430488],[82,0.5158966638127822],[83,0.5202566252361672],[84,0.5209221300707819],[85,0.5250703192626437],[86,0.5043908272214898],[87,0.521973583335542],[88,0.5087644502345938],[89,0.5218627770858196],[90,0.5148901665525045],[91,0.5306362770982863],[92,0.5049721538356001],[93,0.5319589048655567],[95,0.525851007304785],[97,0.5281678122458592],[98,0.515997628490671],[99,0.5374143626104342],[100,0.5266522333549801],[101,0.5418987653683871],[102,0.5355333960615098],[103,0.5457431752680956],[104,0.5444315515342169],[105,0.522255170304561],[107,0.5136118670003876],[108,0.5190874475326078],[109,0.5028901100789275],[110,0.5044150169317921],[111,0.5101552639777461],[112,0.5178639993343191],[113,0.5335625350174572],[114,0.521692445986749],[115,0.5071794004373563],[116,0.5174274733532608],[117,0.5039531867175052],[118,0.5127984874804193],[119,0.5179777843780661],[120,0.5163313820182035],[121,0.5177919873967767],[123,0.5198469370176705],[124,0.5124041707798218],[125,0.5053256714018062],[126,0.520095470744612],[127,0.522812364603548],[128,0.5002761404612102],[129,0.526103941607289],[131,0.5144671783673402],[132,0.49410369648830965],[133,0.49687724035660114],[134,0.5129040537285618],[135,0.5039799640168591],[137,0.5161112143444674],[139,0.5126915628788993],[140,0.5107431607320905],[141,0.5241321433277335],[142,0.5209617538494058],[143,0.5086004101516058],[144,0.509752975932012],[145,0.5146177930097716],[146,0.5279006656492129],[147,0.49989520843761664],[148,0.5312659325172717],[150,0.5186802409152733],[151,0.5152368847921025],[152,0.5012550794247849],[153,0.5157600266781325],[154,0.5157935410291733],[155,0.5196153932144322],[156,0.5354486224678112],[157,0.5187813229937698],[159,0.5089526060619392],[160,0.5376674627016959],[161,0.5366847160815572],[162,0.5194928412868952],[163,0.49937326076906174],[164,0.48732338634727057],[165,0.49280802995781414],[166,0.5204325866361614],[168,0.5167492562128851]],"ce_gatilhada_com":[[0,4.286067068576813],[1,3.78442645072937],[2,4.251819372177124],[3,4.206064701080322],[4,4.17146635055542],[5,4.101698398590088],[6,4.1315319538116455],[7,4.120946168899536],[8,4.161710103352864],[9,3.9559485912323],[10,4.099460999170939],[11,3.9346393942832947],[12,4.041452646255493],[13,4.117861270904541],[14,4.123185555140178],[15,4.128559271494548],[16,3.8645116090774536],[17,4.155359148979187],[18,3.9217464923858643],[19,4.150476614634196],[20,4.165638605753581],[21,3.947313666343689],[22,3.98038117090861],[23,4.1632080078125],[24,3.9268192052841187],[25,4.445131778717041],[26,4.124486366907756],[27,4.188827594121297],[28,3.970947742462158],[29,4.069194475809733],[30,4.014383494853973],[31,3.9760082960128784],[32,4.213275909423828],[33,3.975426971912384],[34,4.02310053507487],[35,4.306819915771484],[36,4.225592494010925],[37,4.282451033592224],[38,4.114863793055217],[39,4.137688159942627],[40,4.103911995887756],[41,4.196538686752319],[42,4.057561695575714],[43,4.078661521275838],[44,4.179943799972534],[45,4.2617199420928955],[47,4.255858580271403],[48,4.140023827552795],[49,3.741398334503174],[50,4.119235038757324],[51,4.031771659851074],[52,3.9705647627512612],[53,4.084055503209432],[54,4.141085386276245],[55,4.178483168284099],[56,4.212615807851155],[57,4.144337832927704],[58,3.8858808676401773],[59,4.024104654788971],[60,4.148428380489349],[61,4.078000624974568],[62,3.881176312764486],[63,3.847168445587158],[64,4.184133688608806],[65,4.027918517589569],[66,4.0433573722839355],[67,3.961278796195984],[69,4.1184407869974775],[70,3.910686731338501],[71,3.93967866897583],[72,4.013786633809407],[73,4.019186178843181],[74,4.334162712097168],[75,4.107743501663208],[76,3.972273667653402],[77,4.025588512420654],[79,4.042650938034058],[80,4.269199013710022],[82,4.015230814615886],[83,3.98799067735672],[84,4.076477448145549],[85,4.049626509348552],[86,4.10666811466217],[87,4.09322464466095],[88,3.9155829747517905],[89,4.225834131240845],[90,4.069340705871582],[91,4.340532541275024],[92,4.16346542040507],[93,4.036631504694621],[95,4.060869137446086],[97,3.7923765182495117],[98,3.9306888580322266],[99,4.378729343414307],[100,4.063064098358154],[101,4.172792434692383],[102,4.144003748893738],[103,4.273817698160808],[104,4.142988204956055],[105,4.482113202412923],[107,4.143183708190918],[108,4.119831562042236],[109,4.278270403544108],[110,4.129132270812988],[111,4.170301914215088],[112,3.9728397130966187],[113,3.758568525314331],[114,4.095796704292297],[115,4.037009358406067],[116,4.185595512390137],[117,4.221568584442139],[118,4.128844261169434],[119,3.9508121013641357],[120,4.078871965408325],[121,4.12814474105835],[123,3.9854937394460044],[124,4.145806789398193],[125,3.86566424369812],[126,4.19337797164917],[127,4.149574279785156],[128,3.934400796890259],[129,4.143209457397461],[131,4.062459548314412],[132,3.9173390865325928],[133,3.9428324699401855],[134,4.136707782745361],[135,3.949597656726837],[137,4.288898468017578],[139,3.954125165939331],[140,4.049332698186238],[141,4.082767486572266],[142,3.909880042076111],[143,4.392031192779541],[144,4.684047698974609],[145,3.856342077255249],[146,4.117315292358398],[147,3.7558157444000244],[148,4.092498302459717],[150,4.005165497461955],[151,4.392730712890625],[152,3.9246556758880615],[153,4.263893286387126],[154,3.8758671283721924],[155,4.027156472206116],[156,4.15762996673584],[157,4.061529636383057],[159,4.307990550994873],[160,4.21621831258138],[161,4.117232322692871],[162,4.242522716522217],[163,3.9759947061538696],[164,4.017006079355876],[165,4.137936115264893],[166,4.100656509399414],[168,4.121177514394124]],"ce_gatilhada_sem":[[0,4.236576080322266],[1,3.8504459857940674],[2,4.179909527301788],[3,4.139123439788818],[4,4.217186450958252],[5,4.084959506988525],[6,4.100430011749268],[7,4.106491267681122],[8,4.166646480560303],[9,4.023962736129761],[10,4.092546542485555],[11,3.9826130867004395],[12,4.02103853225708],[13,4.079225778579712],[14,4.104982693990071],[15,4.248835563659668],[16,3.8635343313217163],[17,4.112422347068787],[18,3.8882867097854614],[19,4.184396584828694],[20,4.111812909444173],[21,3.979639172554016],[22,3.896500031153361],[23,4.195082426071167],[24,3.987370729446411],[25,4.340749740600586],[26,4.094979365666707],[27,4.103381236394246],[28,3.979760487874349],[29,4.05549160639445],[30,3.901759147644043],[31,4.0552486181259155],[32,4.20846160252889],[33,3.987179398536682],[34,4.04586402575175],[35,4.531801700592041],[36,4.198376893997192],[37,4.287527561187744],[38,4.029080073038737],[39,3.901439825693766],[40,4.041401207447052],[41,4.142804463704427],[42,4.0253254771232605],[43,4.059965054194133],[44,4.235558271408081],[45,4.148396074771881],[47,4.196389039357503],[48,4.118307530879974],[49,3.7462127208709717],[50,4.102471828460693],[51,4.082160949707031],[52,3.9799248377482095],[53,3.995748440424601],[54,4.048468172550201],[55,4.110203584035237],[56,4.26343313852946],[57,4.101452052593231],[58,3.83038059870402],[59,3.9796460270881653],[60,4.156876564025879],[61,4.090774695078532],[62,3.8504203160603843],[63,3.903528094291687],[64,4.160703897476196],[65,4.084182262420654],[66,4.082948207855225],[67,3.9158105850219727],[69,4.098624070485433],[70,3.9756551583607993],[71,3.9475344816843667],[72,3.9759419759114585],[73,4.038993199666341],[74,4.29107141494751],[75,4.0856326421101885],[76,3.9688762029012046],[77,4.033683776855469],[79,3.8230553468068442],[80,4.242272257804871],[82,3.999074141184489],[83,3.950379967689514],[84,4.009300231933594],[85,4.055343707402547],[86,4.139273405075073],[87,4.029958367347717],[88,3.920039097468058],[89,4.166723728179932],[90,4.106683651606242],[91,4.293243646621704],[92,4.179000377655029],[93,4.072441180547078],[95,4.234603087107341],[97,3.6487553119659424],[98,3.8856823444366455],[99,4.296202023824056],[100,4.151185989379883],[101,4.162559509277344],[102,4.141791880130768],[103,4.407877127329509],[104,4.13315749168396],[105,4.525223255157471],[107,4.055021286010742],[108,4.0605894724528],[109,4.317263285319011],[110,4.125892877578735],[111,4.135850032170613],[112,4.008356690406799],[113,3.7381067276000977],[114,4.032252848148346],[115,3.9572770595550537],[116,4.168994903564453],[117,4.253632068634033],[118,4.099444627761841],[119,3.824268102645874],[120,4.170312563578288],[121,4.0936397314071655],[123,3.8983155886332193],[124,4.097098112106323],[125,3.8426363468170166],[126,4.21003516515096],[127,4.1745381355285645],[128,3.853309075037638],[129,4.235193252563477],[131,4.085473537445068],[132,3.896311601003011],[133,3.9839794635772705],[134,4.2174296379089355],[135,3.949508249759674],[137,4.21596884727478],[139,3.936429977416992],[140,3.9753963152567544],[141,4.106553554534912],[142,3.808857798576355],[143,4.501911640167236],[144,4.535083293914795],[145,3.7725945313771567],[146,4.136353850364685],[147,3.7206249237060547],[148,3.9946799278259277],[150,3.926337957382202],[151,4.423146724700928],[152,3.8794548511505127],[153,4.181938171386719],[154,3.762695074081421],[155,3.915267825126648],[156,4.133652925491333],[157,4.068828821182251],[159,4.2833571434021],[160,4.143597841262817],[161,4.007633686065674],[162,4.068864345550537],[163,4.069138288497925],[164,3.9895293712615967],[165,4.179645538330078],[166,4.012510061264038],[168,4.184970219930013]],"confianca_media":[[0,0.014005795179400593],[1,0.014750875241588801],[2,0.013995071174576879],[3,0.015515578619670123],[4,0.01565837796078995],[5,0.014830631494987756],[6,0.01566010870737955],[7,0.01335927948821336],[8,0.014753666007891297],[9,0.015552048804238439],[10,0.014737881137989461],[11,0.014153361786156893],[12,0.014869183418340981],[13,0.013509184296708554],[14,0.01480848406208679],[15,0.01520918816095218],[16,0.013993629894685],[17,0.013781262678094208],[18,0.014745595515705645],[19,0.014941285364329815],[20,0.014418500242754817],[21,0.014727286936249584],[22,0.015190432546660304],[23,0.014888688980136067],[24,0.015205525036435574],[25,0.015707991959061474],[26,0.015506478142924607],[27,0.014609291567467153],[28,0.014753540919627994],[29,0.014450355956796557],[30,0.014324338000733405],[31,0.014841560390777886],[32,0.014673139143269509],[33,0.014284636185038835],[34,0.015065727639012039],[35,0.015510563447605819],[36,0.014854351931717247],[37,0.014730984345078468],[38,0.015066820080392063],[39,0.015092760091647506],[40,0.014273391279857606],[41,0.014497175870928913],[42,0.014587165147531778],[43,0.01527150662150234],[44,0.015011837473139167],[45,0.014492718677502126],[47,0.015082574624102563],[48,0.014048298471607268],[49,0.015414570108987391],[50,0.014227683714125305],[51,0.015012298477813601],[52,0.01496647554449737],[53,0.015270379721187055],[54,0.0143581852898933],[55,0.01458142144838348],[56,0.015184173767920583],[57,0.014892731851432472],[58,0.014819184027146548],[59,0.014497090654913336],[60,0.014323995739687234],[61,0.014611113991122693],[62,0.014913753140717745],[63,0.014722652558702976],[64,0.014815373055171221],[65,0.013906444248277694],[66,0.015125164587516338],[67,0.015561614884063601],[69,0.014291771338321269],[70,0.014620006317272782],[71,0.014897825254593045],[72,0.013905479514505714],[73,0.015604188258294016],[74,0.016062231617979705],[75,0.014830976608209312],[76,0.013950062217190862],[77,0.01430337829515338],[79,0.015109260333701968],[80,0.013478024222422391],[82,0.015131825930438936],[83,0.014797488518524915],[84,0.01518187636975199],[85,0.014963558700401336],[86,0.014481423597317189],[87,0.014505581930279732],[88,0.015146418649237603],[89,0.014999470731709152],[90,0.014709505892824382],[91,0.015515368955675513],[92,0.014764821215067059],[93,0.014628206321503967],[95,0.015121456235647202],[97,0.016250888351351023],[98,0.015772839135024697],[99,0.014512922090943903],[100,0.01618180755758658],[101,0.015763344475999475],[102,0.014218972704838961],[103,0.014876901870593429],[104,0.015503104834351689],[105,0.015335924224928021],[107,0.015600815473590046],[108,0.015024530875962228],[109,0.015216328203678131],[110,0.015795795829035342],[111,0.015014436328783631],[112,0.015418317110743374],[113,0.015765588963404298],[114,0.014198667136952281],[115,0.015133468026760966],[116,0.01682953565614298],[117,0.016090929100755602],[118,0.015751290135085583],[119,0.015528962889220566],[120,0.015191744721960276],[121,0.015379938995465636],[123,0.015222843911033124],[124,0.01524947618599981],[125,0.015358433418441564],[126,0.0150985813816078],[127,0.015914147079456598],[128,0.015284448629245162],[129,0.015195142419543117],[131,0.014088986790739],[132,0.015220546629279852],[133,0.0157397078583017],[134,0.016279501374810934],[135,0.014859969029203057],[137,0.01542525930562988],[139,0.015432957326993346],[140,0.015232935547828674],[141,0.015840484818909317],[142,0.016268615960143507],[143,0.01601989078335464],[144,0.01621302729472518],[145,0.015237691288348287],[146,0.014665118651464581],[147,0.01625760964816436],[148,0.015877122757956386],[150,0.014815526083111763],[151,0.016246946528553963],[152,0.016743522719480097],[153,0.015305847628042102],[154,0.016001639945898205],[155,0.01564441656228155],[156,0.015252622310072184],[157,0.015464753669220954],[159,0.016702711349353194],[160,0.015431803010869771],[161,0.016303557436913252],[162,0.015946740983054042],[163,0.01558998430846259],[164,0.014569372695405036],[165,0.016619260422885418],[166,0.015101990196853876],[168,0.015916046977508813]],"confianca_pos_correcao":[[0,0.00017480080714449286],[1,0.00022765935864299536],[2,0.00015713152242824435],[3,0.0002339339116588235],[4,0.0002444746787659824],[5,0.0001832454581744969],[6,0.000212666520383209],[7,0.00017714977730065584],[8,0.0002058160025626421],[9,0.00021500838920474052],[10,0.0001855349401012063],[11,0.00016771513037383556],[12,0.00019946659449487925],[13,0.0001539883087389171],[14,0.00016860227333381772],[15,0.00019665120635181665],[16,0.0001870347186923027],[17,0.00016677798703312874],[18,0.00021856877719983459],[19,0.00019084883388131857],[20,0.00018873269436880946],[21,0.00017354136798530817],[22,0.00021772267064079642],[23,0.0002033693017438054],[24,0.00021594454301521182],[25,0.00022691325284540653],[26,0.00019661954138427973],[27,0.000205742078833282],[28,0.00020021619275212288],[29,0.00020490586757659912],[30,0.00019783212337642908],[31,0.00023099023383110762],[32,0.00020094914361834526],[33,0.00018624967196956277],[34,0.00020409660646691918],[35,0.00023455044720321894],[36,0.00020131113706156611],[37,0.0001635266817174852],[38,0.00020095612853765488],[39,0.00019825849449262023],[40,0.0001805524225346744],[41,0.00020626740297302604],[42,0.0001861589844338596],[43,0.00020459864754229784],[44,0.00021823804127052426],[45,0.00016008649254217744],[47,0.0001973610487766564],[48,0.00016119005158543587],[49,0.0002441523829475045],[50,0.0002091362257488072],[51,0.00023523153504356742],[52,0.0001829317770898342],[53,0.0002044782740995288],[54,0.00019435991998761892],[55,0.00017329840920865536],[56,0.0001970849698409438],[57,0.0001862524077296257],[58,0.00020091136684641242],[59,0.0001844773651100695],[60,0.0001953413593582809],[61,0.00021971168462187052],[62,0.0001874128938652575],[63,0.0002300082123838365],[64,0.00020098540699109435],[65,0.00018308439757674932],[66,0.0002244594506919384],[67,0.00023363175569102168],[69,0.00018597597954794765],[70,0.00022133812308311462],[71,0.00021992012625560164],[72,0.0001862238859757781],[73,0.00022797408746555448],[74,0.00025491410633549094],[75,0.0002235470456071198],[76,0.0002015726058743894],[77,0.0001932792365550995],[79,0.00019967812113463879],[80,0.00017906073480844498],[82,0.00021547259530052543],[83,0.00019329809583723545],[84,0.00022009160602465272],[85,0.000212631537579],[86,0.000200836977455765],[87,0.00019918876932933927],[88,0.0002124322927556932],[89,0.0002293490106239915],[90,0.0002115644747391343],[91,0.000244827417191118],[92,0.00020085048163309693],[93,0.00019380770390853286],[95,0.00020603096345439553],[97,0.0002520332927815616],[98,0.00025127234403043985],[99,0.00020745792426168919],[100,0.00025885639479383826],[101,0.00024917098926380277],[102,0.0002033407217822969],[103,0.00021373038180172443],[104,0.00021221203496679664],[105,0.00023959926329553127],[107,0.0002038688980974257],[108,0.00022614729823544621],[109,0.00022665399592369795],[110,0.0002603549510240555],[111,0.00021242350339889526],[112,0.0002445070422254503],[113,0.00026293762493878603],[114,0.00019476859597489238],[115,0.00023241608869284391],[116,0.000262527319137007],[117,0.0002568644122220576],[118,0.00021286855917423964],[119,0.00022602855460718274],[120,0.00022025423822924495],[121,0.0002374387113377452],[123,0.00020761089399456978],[124,0.0002183683100156486],[125,0.0002477207453921437],[126,0.0001946083502843976],[127,0.00026336940936744213],[128,0.00023416581097990274],[129,0.00023831374710425735],[131,0.00020483590196818113],[132,0.00020405568648129702],[133,0.00023210106883198023],[134,0.0002538092085160315],[135,0.0002119153505191207],[137,0.0002413597539998591],[139,0.0002151712542399764],[140,0.00020674982806667686],[141,0.00024042773293331265],[142,0.0002533081569708884],[143,0.0002512556384317577],[144,0.0002632568357512355],[145,0.0002334991004317999],[146,0.00019624910783022642],[147,0.0002612632233649492],[148,0.00023533200146630406],[150,0.00021366012515500188],[151,0.00026674894616007805],[152,0.000263556488789618],[153,0.0002226493670605123],[154,0.0002479103277437389],[155,0.0002239931491203606],[156,0.00023717043222859502],[157,0.00023646448971703649],[159,0.00025883811758831143],[160,0.0002061129198409617],[161,0.00025759817799553275],[162,0.0002580241416580975],[163,0.00020040024537593126],[164,0.0002222979674115777],[165,0.00026956608053296804],[166,0.0002270553377456963],[168,0.00023483711993321776]],"ece_calibracao":[[0,0.0153643210651353]],"ece_pos_calibracao":[[0,0.00025579490466043353]],"escore_aprendizado":[[0,0.0],[25,0.1875],[50,0.1875],[75,0.18916117956760387],[100,0.20438043290798483],[125,0.1728106360636155],[150,0.19513877474901606]],"lr":[[0,0.000011249999999999999],[1,0.000011249999999999999],[2,0.000011249999999999999],[3,0.000011249999999999999],[4,0.000011249999999999999],[5,0.000011249999999999999],[6,0.000011249999999999999],[7,0.000011249999999999999],[8,0.000011249999999999999],[9,0.000011249999999999999],[10,0.000011249999999999999],[11,0.000011249999999999999],[12,0.000011249999999999999],[13,0.000011249999999999999],[14,0.000011249999999999999],[15,0.000011249999999999999],[16,0.000011249999999999999],[17,0.000011249999999999999],[18,0.000011249999999999999],[19,0.000011249999999999999],[20,0.000011249999999999999],[21,0.000011249999999999999],[22,0.000011249999999999999],[23,0.000011249999999999999],[24,0.000011249999999999999],[25,0.000011249999999999999],[26,0.000011249999999999999],[27,0.000011249999999999999],[28,0.000011249999999999999],[29,0.000011249999999999999],[30,0.000011249999999999999],[31,0.000011249999999999999],[32,0.000011249999999999999],[33,0.000011249999999999999],[34,0.000011249999999999999],[35,0.000011249999999999999],[36,0.000011249999999999999],[37,0.000011249999999999999],[38,0.000011249999999999999],[39,0.000011249999999999999],[40,0.000011249999999999999],[41,0.000011249999999999999],[42,0.000011249999999999999],[43,0.000011249999999999999],[44,0.000011249999999999999],[45,0.000011249999999999999],[46,0.000011249999999999999],[47,0.000011249999999999999],[48,0.000011249999999999999],[49,0.000011249999999999999],[50,0.000011249999999999999],[51,0.000011249999999999999],[52,0.000011249999999999999],[53,0.000011249999999999999],[54,0.000011249999999999999],[55,0.000011249999999999999],[56,0.000011249999999999999],[57,0.000011249999999999999],[58,0.000011249999999999999],[59,0.000011249999999999999],[60,0.000011249999999999999],[61,0.000011249999999999999],[62,0.000011249999999999999],[63,0.000011249999999999999],[64,0.000011249999999999999],[65,0.000011249999999999999],[66,0.000011249999999999999],[67,0.000011249999999999999],[68,0.000011249999999999999],[69,0.000011249999999999999],[70,0.000011249999999999999],[71,0.000011249999999999999],[72,0.000011249999999999999],[73,0.000011249999999999999],[74,0.000011249999999999999],[75,0.000011249999999999999],[76,0.000011249999999999999],[77,0.000011249999999999999],[78,0.000011249999999999999],[79,0.000011249999999999999],[80,0.000011249999999999999],[81,0.000011249999999999999],[82,0.000011249999999999999],[83,0.000011249999999999999],[84,0.000011249999999999999],[85,0.000011249999999999999],[86,0.000011249999999999999],[87,0.000011249999999999999],[88,0.000011249999999999999],[89,0.000011249999999999999],[90,0.000011249999999999999],[91,0.000011249999999999999],[92,0.000011249999999999999],[93,0.000011249999999999999],[94,0.000011249999999999999],[95,0.000011249999999999999],[96,0.000011249999999999999],[97,0.000011249999999999999],[98,0.000011249999999999999],[99,0.000011249999999999999],[100,0.000011249999999999999],[101,0.000011249999999999999],[102,0.000011249999999999999],[103,0.000011249999999999999],[104,0.000011249999999999999],[105,0.000011249999999999999],[106,0.000011249999999999999],[107,0.000011249999999999999],[108,0.000011249999999999999],[109,0.000011249999999999999],[110,0.000011249999999999999],[111,0.000011249999999999999],[112,0.000011249999999999999],[113,0.000011249999999999999],[114,0.000011249999999999999],[115,0.000011249999999999999],[116,0.000011249999999999999],[117,0.000011249999999999999],[118,0.000011249999999999999],[119,0.000011249999999999999],[120,0.000011249999999999999],[121,0.000011249999999999999],[122,0.000011249999999999999],[123,0.000011249999999999999],[124,0.000011249999999999999],[125,0.000011249999999999999],[126,0.000011249999999999999],[127,0.000011249999999999999],[128,0.000011249999999999999],[129,0.000011249999999999999],[130,0.000011249999999999999],[131,0.000011249999999999999],[132,0.000011249999999999999],[133,0.000011249999999999999],[134,0.000011249999999999999],[135,0.000011249999999999999],[136,0.000011249999999999999],[137,0.000011249999999999999],[138,0.000011249999999999999],[139,0.000011249999999999999],[140,0.000011249999999999999],[141,0.000011249999999999999],[142,0.000011249999999999999],[143,0.000011249999999999999],[144,0.000011249999999999999],[145,0.000011249999999999999],[146,0.000011249999999999999],[147,0.000011249999999999999],[148,0.000011249999999999999],[149,0.000011249999999999999],[150,0.000011249999999999999],[151,0.000011249999999999999],[152,0.000011249999999999999],[153,0.000011249999999999999],[154,0.000011249999999999999],[155,0.000011249999999999999],[156,0.000011249999999999999],[157,0.000011249999999999999],[158,0.000011249999999999999],[159,0.000011249999999999999],[160,0.000011249999999999999],[161,0.000011249999999999999],[162,0.000011249999999999999],[163,0.000011249999999999999],[164,0.000011249999999999999],[165,0.000011249999999999999],[166,0.000011249999999999999],[167,0.000011249999999999999],[168,0.000011249999999999999]],"norma_delta":[[0,1.708319136707895],[1,1.7300068602673995],[2,1.620430610788343],[3,1.6064793730954128],[4,1.6167468516465278],[5,1.6468828291589281],[6,1.7350913278250095],[7,1.630215345211204],[8,1.6182122416164193],[9,1.58172762747616],[10,1.5862192739843068],[11,1.537775653846526],[12,1.5631270866883453],[13,1.544141183935557],[14,1.5134111733660451],[15,1.4661356607720015],[16,1.4392390305454088],[17,1.4181713453430784],[18,1.456519070108581],[19,1.460183178371191],[20,1.611386347788068],[21,1.5828792606896802],[22,1.5255413689169421],[23,1.5640518111893598],[24,1.4022754875719121],[25,1.4444649350514327],[26,1.4812679498928891],[27,1.4289980200346035],[28,1.4762965371518995],[29,1.3770961822433576],[30,1.4371660093329364],[31,1.4730678964519501],[32,1.5380112209034758],[33,1.4495824475718342],[34,1.5779715737271203],[35,1.5198499644417052],[36,1.3533310977716884],[37,1.3752618020249405],[38,1.353610785415756],[39,1.456385001281391],[40,1.3383191835424233],[41,1.3290010143761248],[42,1.511221870558133],[43,1.3418453379934163],[44,1.4758589748838191],[45,1.3574947639490806],[47,1.4525553909744293],[48,1.4618763890123232],[49,1.347110051579298],[50,1.3528595802819194],[51,1.3840467472863809],[52,1.387818021670968],[53,1.3366051505782275],[54,1.32279419019154],[55,1.390998875796469],[56,1.2652916174833213],[57,1.3189911861342105],[58,1.3317465827572939],[59,1.278195904767957],[60,1.3196694879298554],[61,1.280592447279523],[62,1.3733123045224886],[63,1.2646379271552872],[64,1.1588427368057588],[65,1.156884560568702],[66,1.1870449097613038],[67,1.2957338115012818],[69,1.2749810722292494],[70,1.2038063993145407],[71,1.3324778415061387],[72,1.2886111307296062],[73,1.2648392109969129],[74,1.3410594974122776],[75,1.1354759212512584],[76,1.2331790683684873],[77,1.189638771640388],[79,1.2700270872906954],[80,1.2631520258878008],[82,1.2665849506638827],[83,1.3629588535644444],[84,1.2180280258700165],[85,1.2884129592812232],[86,1.2489547650962596],[87,1.0846458272751087],[88,1.243722746660693],[89,1.2191918379157485],[90,1.1855748281687055],[91,1.1390997212088558],[92,1.2188929449518349],[93,1.2005518359679215],[95,1.1190130617096998],[97,1.1573478538591837],[98,1.1648882810701517],[99,1.2371362151452496],[100,1.2569962690475558],[101,1.1831173583286196],[102,1.2085116298717267],[103,1.1291979244640111],[104,1.201604749633733],[105,1.1457088518438527],[107,1.1485340311391798],[108,1.1089015003934155],[109,1.1838074041222357],[110,1.104654504264344],[111,1.2527658380661706],[112,1.1583097000364044],[113,1.210884437629006],[114,1.085769475658132],[115,1.214483765871705],[116,1.137212324969565],[117,1.1746103533482894],[118,1.0414256134865818],[119,1.1267369424940639],[120,1.12564100441577],[121,1.2051030037412402],[123,1.0976015667852725],[124,1.1226575093537703],[125,1.1604684737104891],[126,1.1155392418819374],[127,1.0652919438987012],[128,1.1531150176487175],[129,1.1267395875025201],[131,1.083873131344891],[132,1.1365614844530847],[133,1.047241203823016],[134,1.1385807373844454],[135,1.1053918063611534],[137,1.1117059638547373],[139,1.042017241930218],[140,1.1543223834890337],[141,1.006048388453089],[142,1.1120681846180498],[143,1.0850699822318546],[144,1.152530667937517],[145,1.0979651834286943],[146,1.0850081824068851],[147,1.0496761935352823],[148,1.0087484109669853],[150,1.12568257076071],[151,1.0016805713109511],[152,1.092444976451071],[153,1.0588519918053854],[154,1.0527136522847345],[155,1.1514062203479627],[156,1.0948189552649488],[157,1.2063021633748292],[159,1.1195431396534503],[160,1.1265771195604581],[161,1.0479011630560298],[162,1.0783949251484246],[163,1.1299022781686716],[164,0.9900871934929211],[165,1.0888536666235924],[166,1.06340336263088],[168,1.052118974340061]],"norma_grad":[[0,1.0706944308072193],[1,1.058681324364581],[2,1.1212959538364802],[3,1.067895002970803],[4,1.0221774556666745],[5,1.1322959163535613],[6,0.9451653603302832],[7,1.133231477810606],[8,0.9629135068457095],[9,1.1440372511260901],[10,1.0250293261238235],[11,1.0741438322569754],[12,0.9900071161341525],[13,1.0652630577576816],[14,0.9925714877803088],[15,1.0815214285702246],[16,1.085071210889009],[17,1.0027861152551143],[18,1.1611169873507472],[19,1.0277922154742343],[20,1.0258883708770539],[21,1.0553671644259905],[22,1.1860490818348093],[23,1.0049952349265974],[24,1.2025157458131985],[25,1.0758710303534005],[26,1.0286529522463903],[27,1.1035193936426937],[28,1.1706898808955433],[29,0.934378912036096],[30,0.9523699057809005],[31,1.0676269911699185],[32,0.908219595193368],[33,1.048290953824074],[34,1.05611356928056],[35,1.2203281203048992],[36,0.978712950980061],[37,1.0967026057492426],[38,1.0500436389194803],[39,1.0012882132084042],[40,1.070366878192443],[41,1.106372938845751],[42,1.026086134823049],[43,0.9621761844129753],[44,0.9465230415374172],[45,1.1515258615533885],[46,2.4880620364110393],[47,1.1429099486515262],[48,1.123578630103584],[49,1.04529266755212],[50,0.9362884604777684],[51,1.01742220860724],[52,0.9887578683884435],[53,1.118654215605481],[54,1.0954787084331175],[55,0.9698131901315115],[56,1.0815758545536378],[57,0.94367744354691],[58,1.0342872998811727],[59,1.011731697185766],[60,1.0038751795627259],[61,1.0228202503586676],[62,1.1114216248905815],[63,1.0683394319639308],[64,1.067316328405041],[65,1.1017392036568525],[66,1.1063406771889732],[67,1.1486115271919812],[68,2.0060731867681363],[69,1.03400668960407],[70,1.0609017606343112],[71,1.1349785645952788],[72,1.1307615793132317],[73,1.1410978117509247],[74,1.029681487178669],[75,0.9760702443163086],[76,1.0160994948314208],[77,1.0259825289020923],[78,2.0652311744261644],[79,1.0254469298815283],[80,1.2283622860196843],[81,1.9427432271429104],[82,1.2591149589447603],[83,1.1627978503638006],[84,1.1258199272221108],[85,0.9979682951287733],[86,0.9911655906139275],[87,1.0381336361448792],[88,0.9469640390069516],[89,1.0673503503007749],[90,0.9482895252655693],[91,1.0054993830562238],[92,0.9351294248604523],[93,1.08905903966354],[94,2.2728295653693187],[95,1.0165528557116166],[96,2.081000329307827],[97,1.0749726713775412],[98,1.097819977775046],[99,1.1961683629622863],[100,1.066813955606253],[101,0.9379904717435616],[102,0.9002086569846353],[103,1.0912383817144184],[104,1.0023110467072986],[105,1.042779209151138],[106,2.2722567426413143],[107,1.0866133489864995],[108,1.0103594926845474],[109,0.9353747264493332],[110,0.9978372436539392],[111,1.0503458611559642],[112,0.9647271169237962],[113,1.0609203512471437],[114,0.9833851880972099],[115,1.0848190285322623],[116,0.9278133592316041],[117,0.9799770873592879],[118,1.0817906131934178],[119,1.0457924593856769],[120,1.149201121452088],[121,1.0168879462651663],[122,2.08240591146685],[123,1.042936334593993],[124,0.9021904685947577],[125,1.021547972730812],[126,0.9409410481047601],[127,1.0726039967396008],[128,1.096295402477455],[129,1.0157976661676993],[130,2.013369647647703],[131,1.0518913867439483],[132,1.1652867017785586],[133,1.0560204885995625],[134,1.0095111671990609],[135,0.9931071115427105],[136,1.8304942913841677],[137,1.1152381217464264],[138,2.114200304015639],[139,1.0253745554496478],[140,1.148482648016839],[141,1.1618790882413483],[142,1.0858387682340214],[143,1.129375061281943],[144,1.1324345533909195],[145,1.07894197541203],[146,1.048983095963566],[147,0.9640507992658075],[148,0.9176863306346016],[149,1.9747932007757423],[150,0.9647574265850958],[151,1.1535362738993074],[152,0.9000584109802485],[153,1.128968650286262],[154,1.102696398064407],[155,1.0537699729713836],[156,1.0929387846657175],[157,0.982282781196922],[158,1.9586330684395261],[159,1.1747823229577194],[160,1.0449568198697858],[161,0.9981421271077437],[162,1.0931335762675405],[163,1.1550413208928894],[164,1.0062815385564794],[165,0.9840973031847245],[166,1.081734827060569],[167,2.045080408389055],[168,0.9897024819661612]],"perda":[[0,4.019904613494873],[1,3.8765830993652344],[2,4.043992519378662],[3,3.9648988246917725],[4,3.9649620056152344],[5,3.8849925994873047],[6,3.96303653717041],[7,3.9489879608154297],[8,3.9754014015197754],[9,3.9448533058166504],[10,3.889578104019165],[11,3.9643964767456055],[12,4.004512786865234],[13,4.031096935272217],[14,3.901010751724243],[15,3.935734510421753],[16,3.8640897274017334],[17,4.226799964904785],[18,3.8076579570770264],[19,4.0520524978637695],[20,3.9894328117370605],[21,3.9299263954162598],[22,3.901426315307617],[23,3.9511356353759766],[24,4.033718109130859],[25,3.9283969402313232],[26,3.9959826469421387],[27,4.066652297973633],[28,3.9744462966918945],[29,3.9650230407714844],[30,3.8737144470214844],[31,3.8420209884643555],[32,4.01137638092041],[33,4.020869255065918],[34,3.924563407897949],[35,3.922980308532715],[36,4.158365249633789],[37,4.142971038818359],[38,4.001399040222168],[39,4.008262634277344],[40,4.105016708374023],[41,3.997499465942383],[42,3.8311593532562256],[43,3.949859142303467],[44,3.9626147747039795],[45,4.067758560180664],[46,3.8306237970079695],[47,3.9733080863952637],[48,4.04431676864624],[49,4.03847599029541],[50,4.01676607131958],[51,3.905447244644165],[52,3.970186233520508],[53,3.822218894958496],[54,3.9865007400512695],[55,4.027141571044922],[56,3.841069459915161],[57,3.93839168548584],[58,4.0042500495910645],[59,3.9349210262298584],[60,4.002318859100342],[61,3.987222671508789],[62,3.8283700942993164],[63,3.762843132019043],[64,3.967745542526245],[65,3.9859542846679688],[66,3.940972328186035],[67,3.8698270320892334],[68,4.028727463313511],[69,3.9312496185302734],[70,3.976386785507202],[71,3.9771463871002197],[72,3.912465810775757],[73,4.0237135887146],[74,3.9623794555664062],[75,4.015510559082031],[76,4.007643222808838],[77,3.895439863204956],[78,3.958944150379726],[79,4.04854679107666],[80,3.99484920501709],[81,3.8573156084333147],[82,3.934675693511963],[83,3.7946627140045166],[84,3.8712849617004395],[85,3.969369411468506],[86,3.8854079246520996],[87,4.019211769104004],[88,4.074251174926758],[89,4.063762187957764],[90,4.006500244140625],[91,3.893702745437622],[92,3.9659714698791504],[93,3.8546764850616455],[94,3.848675489425659],[95,4.120311260223389],[96,3.9609663486480713],[97,3.969033718109131],[98,3.950951099395752],[99,4.096883773803711],[100,4.016682147979736],[101,3.910559892654419],[102,3.9503703117370605],[103,3.920416831970215],[104,4.056010723114014],[105,4.092321395874023],[106,4.0855579035622736],[107,4.009933948516846],[108,3.976130247116089],[109,3.931131362915039],[110,4.051743030548096],[111,3.9072914123535156],[112,3.892509937286377],[113,4.116713523864746],[114,3.983604669570923],[115,3.8645997047424316],[116,4.04237699508667],[117,3.9437155723571777],[118,3.9830925464630127],[119,3.9839518070220947],[120,3.8003838062286377],[121,3.933864116668701],[122,3.909356185368129],[123,3.958214521408081],[124,3.9459877014160156],[125,4.046004295349121],[126,3.8863306045532227],[127,4.236734390258789],[128,3.886537790298462],[129,3.9743528366088867],[130,4.055516992296491],[131,3.902364730834961],[132,3.9039292335510254],[133,3.997091293334961],[134,4.046995639801025],[135,3.794684886932373],[136,4.039335761751447],[137,3.9826745986938477],[138,4.041910478046963],[139,3.897308349609375],[140,3.861067056655884],[141,4.077580451965332],[142,4.080158233642578],[143,3.87811279296875],[144,3.9761674404144287],[145,4.0868120193481445],[146,3.9624648094177246],[147,3.9705681800842285],[148,3.940629005432129],[149,3.9118497371673584],[150,3.9766533374786377],[151,3.7224082946777344],[152,3.8151743412017822],[153,3.973607063293457],[154,3.8451178073883057],[155,3.9515340328216553],[156,3.9265127182006836],[157,3.9968042373657227],[158,3.921729496547154],[159,3.977842092514038],[160,4.039999008178711],[161,4.068119049072266],[162,3.9798154830932617],[163,3.974337339401245],[164,4.024538516998291],[165,3.890970468521118],[166,4.052980899810791],[167,4.031119687216623],[168,3.889193534851074]],"taxa_gatilho":[[0,1.0],[1,0.25],[2,1.0],[3,0.25],[4,0.25],[5,0.75],[6,0.5],[7,1.0],[8,0.75],[9,0.5],[10,0.75],[11,1.0],[12,0.75],[13,1.0],[14,0.75],[15,0.75],[16,1.0],[17,1.0],[18,0.5],[19,0.75],[20,0.75],[21,1.0],[22,0.75],[23,0.5],[24,0.5],[25,0.25],[26,0.75],[27,0.75],[28,0.75],[29,0.75],[30,1.0],[31,0.5],[32,0.75],[33,1.0],[34,0.75],[35,0.25],[36,1.0],[37,1.0],[38,0.75],[39,0.75],[40,1.0],[41,0.75],[42,1.0],[43,0.75],[44,0.5],[45,1.0],[46,0.0],[47,0.75],[48,1.0],[49,0.25],[50,0.75],[51,0.5],[52,0.75],[53,0.75],[54,1.0],[55,0.75],[56,0.75],[57,1.0],[58,0.75],[59,1.0],[60,1.0],[61,0.75],[62,0.75],[63,0.5],[64,0.75],[65,1.0],[66,0.5],[67,0.5],[68,0.0],[69,0.75],[70,0.75],[71,0.75],[72,0.75],[73,0.75],[74,0.25],[75,0.75],[76,0.75],[77,1.0],[78,0.0],[79,0.75],[80,1.0],[81,0.0],[82,0.75],[83,1.0],[84,0.75],[85,0.75],[86,1.0],[87,1.0],[88,0.75],[89,0.5],[90,0.75],[91,0.5],[92,0.75],[93,0.75],[94,0.0],[95,0.75],[96,0.0],[97,0.25],[98,0.25],[99,0.75],[100,0.25],[101,0.25],[102,1.0],[103,0.75],[104,0.5],[105,0.75],[106,0.0],[107,0.5],[108,0.75],[109,0.75],[110,0.5],[111,0.75],[112,0.5],[113,0.25],[114,1.0],[115,0.5],[116,0.25],[117,0.25],[118,0.5],[119,0.5],[120,0.75],[121,0.5],[122,0.0],[123,0.75],[124,0.75],[125,0.25],[126,0.75],[127,0.25],[128,0.75],[129,0.25],[130,0.0],[131,0.75],[132,0.75],[133,0.5],[134,0.25],[135,1.0],[136,0.0],[137,0.5],[138,0.0],[139,0.5],[140,0.75],[141,0.25],[142,0.5],[143,0.25],[144,0.25],[145,0.75],[146,1.0],[147,0.25],[148,0.5],[149,0.0],[150,0.75],[151,0.25],[152,0.25],[153,0.75],[154,0.25],[155,0.5],[156,0.5],[157,0.75],[158,0.0],[159,0.25],[160,0.75],[161,0.25],[162,0.25],[163,0.5],[164,0.75],[165,0.25],[166,0.5],[167,0.0],[168,0.75]],"temperatura_calibrada":[[0,8.0]]}} +{"tipo":"ajuste","dados":{"passo":0,"parametro":"pensamento_tau_c","de":0.3,"para":0.016845226287841797,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, calibração inicial fase C)"}} +{"tipo":"ajuste","dados":{"passo":0,"parametro":"pensamento_tau_c","de":0.016845226287841797,"para":0.016435205936431885,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=1024)"}} +{"tipo":"ajuste","dados":{"passo":1,"parametro":"pensamento_tau_c","de":0.016435205936431885,"para":0.016585886478424072,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=2048)"}} +{"tipo":"ajuste","dados":{"passo":2,"parametro":"pensamento_tau_c","de":0.016585886478424072,"para":0.016940951347351074,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=3072)"}} +{"tipo":"ajuste","dados":{"passo":3,"parametro":"pensamento_tau_c","de":0.016940951347351074,"para":0.016585886478424072,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=4096)"}} +{"tipo":"ajuste","dados":{"passo":4,"parametro":"pensamento_tau_c","de":0.016585886478424072,"para":0.01655721664428711,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=5120)"}} +{"tipo":"ajuste","dados":{"passo":5,"parametro":"pensamento_tau_c","de":0.01655721664428711,"para":0.016604244709014893,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=6144)"}} +{"tipo":"ajuste","dados":{"passo":6,"parametro":"pensamento_tau_c","de":0.016604244709014893,"para":0.016614198684692383,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=7168)"}} +{"tipo":"ajuste","dados":{"passo":7,"parametro":"pensamento_tau_c","de":0.016614198684692383,"para":0.016540348529815674,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=8192)"}} +{"tipo":"ajuste","dados":{"passo":8,"parametro":"pensamento_tau_c","de":0.016540348529815674,"para":0.016441524028778076,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=9216)"}} +{"tipo":"ajuste","dados":{"passo":9,"parametro":"pensamento_tau_c","de":0.016441524028778076,"para":0.01641368865966797,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=10240)"}} +{"tipo":"ajuste","dados":{"passo":10,"parametro":"pensamento_tau_c","de":0.01641368865966797,"para":0.016344547271728516,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=11264)"}} +{"tipo":"ajuste","dados":{"passo":11,"parametro":"pensamento_tau_c","de":0.016344547271728516,"para":0.016338109970092773,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=12288)"}} +{"tipo":"ajuste","dados":{"passo":12,"parametro":"pensamento_tau_c","de":0.016338109970092773,"para":0.01634281873703003,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=13312)"}} +{"tipo":"ajuste","dados":{"passo":13,"parametro":"pensamento_tau_c","de":0.01634281873703003,"para":0.016324341297149658,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=14336)"}} +{"tipo":"ajuste","dados":{"passo":14,"parametro":"pensamento_tau_c","de":0.016324341297149658,"para":0.016304314136505127,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=15360)"}} +{"tipo":"ajuste","dados":{"passo":15,"parametro":"pensamento_tau_c","de":0.016304314136505127,"para":0.016326427459716797,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=16384)"}} +{"tipo":"ajuste","dados":{"passo":15,"parametro":"pensamento_n_tentativas","de":4.0,"para":8.0,"motivo":"A-3 Hoeffding+acordo (â=0.501 ⇒ p̂=0.518, δ=0.05)"}} +{"tipo":"ajuste","dados":{"passo":16,"parametro":"pensamento_tau_c","de":0.016326427459716797,"para":0.016365766525268555,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=17408)"}} +{"tipo":"ajuste","dados":{"passo":17,"parametro":"pensamento_tau_c","de":0.016365766525268555,"para":0.01637744903564453,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=18432)"}} +{"tipo":"ajuste","dados":{"passo":18,"parametro":"pensamento_tau_c","de":0.01637744903564453,"para":0.016354262828826904,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=19456)"}} +{"tipo":"ajuste","dados":{"passo":19,"parametro":"pensamento_tau_c","de":0.016354262828826904,"para":0.016344785690307617,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=20480)"}} +{"tipo":"ajuste","dados":{"passo":20,"parametro":"pensamento_tau_c","de":0.016344785690307617,"para":0.016359567642211914,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=21504)"}} +{"tipo":"ajuste","dados":{"passo":21,"parametro":"pensamento_tau_c","de":0.016359567642211914,"para":0.016378700733184814,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=22528)"}} +{"tipo":"ajuste","dados":{"passo":22,"parametro":"pensamento_tau_c","de":0.016378700733184814,"para":0.016353905200958252,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=23552)"}} +{"tipo":"ajuste","dados":{"passo":23,"parametro":"pensamento_tau_c","de":0.016353905200958252,"para":0.016379952430725098,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=24576)"}} +{"tipo":"ajuste","dados":{"passo":24,"parametro":"pensamento_tau_c","de":0.016379952430725098,"para":0.016350626945495605,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=25600)"}} +{"tipo":"ajuste","dados":{"passo":25,"parametro":"pensamento_tau_c","de":0.016350626945495605,"para":0.016386866569519043,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=26624)"}} +{"tipo":"ajuste","dados":{"passo":26,"parametro":"pensamento_tau_c","de":0.016386866569519043,"para":0.016405999660491943,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=27648)"}} +{"tipo":"ajuste","dados":{"passo":27,"parametro":"pensamento_tau_c","de":0.016405999660491943,"para":0.016394197940826416,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=28672)"}} +{"tipo":"ajuste","dados":{"passo":28,"parametro":"pensamento_tau_c","de":0.016394197940826416,"para":0.016412436962127686,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=29696)"}} +{"tipo":"ajuste","dados":{"passo":29,"parametro":"pensamento_tau_c","de":0.016412436962127686,"para":0.016422152519226074,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=30720)"}} +{"tipo":"ajuste","dados":{"passo":31,"parametro":"pensamento_tau_c","de":0.016422152519226074,"para":0.016438424587249756,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=32768)"}} +{"tipo":"ajuste","dados":{"passo":32,"parametro":"pensamento_tau_c","de":0.016438424587249756,"para":0.016441285610198975,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=33792)"}} +{"tipo":"ajuste","dados":{"passo":33,"parametro":"pensamento_tau_c","de":0.016441285610198975,"para":0.0164492130279541,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=34816)"}} +{"tipo":"ajuste","dados":{"passo":34,"parametro":"pensamento_tau_c","de":0.0164492130279541,"para":0.016452670097351074,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=35840)"}} +{"tipo":"ajuste","dados":{"passo":35,"parametro":"pensamento_tau_c","de":0.016452670097351074,"para":0.016454696655273438,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=36864)"}} +{"tipo":"ajuste","dados":{"passo":36,"parametro":"pensamento_tau_c","de":0.016454696655273438,"para":0.016450822353363037,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=37888)"}} +{"tipo":"ajuste","dados":{"passo":37,"parametro":"pensamento_tau_c","de":0.016450822353363037,"para":0.01643693447113037,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=38912)"}} +{"tipo":"ajuste","dados":{"passo":38,"parametro":"pensamento_tau_c","de":0.01643693447113037,"para":0.016421377658843994,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=39936)"}} +{"tipo":"ajuste","dados":{"passo":39,"parametro":"pensamento_tau_c","de":0.016421377658843994,"para":0.016398966312408447,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=40960)"}} +{"tipo":"ajuste","dados":{"passo":40,"parametro":"pensamento_tau_c","de":0.016398966312408447,"para":0.01640540361404419,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=41984)"}} +{"tipo":"ajuste","dados":{"passo":41,"parametro":"pensamento_tau_c","de":0.01640540361404419,"para":0.016411244869232178,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=43008)"}} +{"tipo":"ajuste","dados":{"passo":42,"parametro":"pensamento_tau_c","de":0.016411244869232178,"para":0.016428828239440918,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=44032)"}} +{"tipo":"ajuste","dados":{"passo":43,"parametro":"pensamento_tau_c","de":0.016428828239440918,"para":0.016434311866760254,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=45056)"}} +{"tipo":"ajuste","dados":{"passo":44,"parametro":"pensamento_tau_c","de":0.016434311866760254,"para":0.01643925905227661,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=46080)"}} +{"tipo":"ajuste","dados":{"passo":45,"parametro":"pensamento_tau_c","de":0.01643925905227661,"para":0.01643669605255127,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=47104)"}} +{"tipo":"ajuste","dados":{"passo":46,"parametro":"pensamento_tau_c","de":0.01643669605255127,"para":0.016446471214294434,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=48128)"}} +{"tipo":"ajuste","dados":{"passo":47,"parametro":"pensamento_tau_c","de":0.016446471214294434,"para":0.0164492130279541,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=49152)"}} +{"tipo":"ajuste","dados":{"passo":48,"parametro":"pensamento_tau_c","de":0.0164492130279541,"para":0.01646798849105835,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=50176)"}} +{"tipo":"ajuste","dados":{"passo":49,"parametro":"pensamento_tau_c","de":0.01646798849105835,"para":0.01648569107055664,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=51200)"}} +{"tipo":"ajuste","dados":{"passo":50,"parametro":"pensamento_tau_c","de":0.01648569107055664,"para":0.01650172472000122,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=52224)"}} +{"tipo":"ajuste","dados":{"passo":51,"parametro":"pensamento_tau_c","de":0.01650172472000122,"para":0.016518115997314453,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=53248)"}} +{"tipo":"ajuste","dados":{"passo":53,"parametro":"pensamento_tau_c","de":0.016518115997314453,"para":0.016523420810699463,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=55296)"}} +{"tipo":"ajuste","dados":{"passo":54,"parametro":"pensamento_tau_c","de":0.016523420810699463,"para":0.016537487506866455,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=56320)"}} +{"tipo":"ajuste","dados":{"passo":56,"parametro":"pensamento_tau_c","de":0.016537487506866455,"para":0.016536355018615723,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=58368)"}} +{"tipo":"ajuste","dados":{"passo":58,"parametro":"pensamento_tau_c","de":0.016536355018615723,"para":0.01655411720275879,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=59392)"}} +{"tipo":"ajuste","dados":{"passo":59,"parametro":"pensamento_tau_c","de":0.01655411720275879,"para":0.016570985317230225,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=60416)"}} +{"tipo":"ajuste","dados":{"passo":60,"parametro":"pensamento_tau_c","de":0.016570985317230225,"para":0.01659071445465088,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=61440)"}} +{"tipo":"ajuste","dados":{"passo":61,"parametro":"pensamento_tau_c","de":0.01659071445465088,"para":0.016592085361480713,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=62464)"}} +{"tipo":"ajuste","dados":{"passo":62,"parametro":"pensamento_tau_c","de":0.016592085361480713,"para":0.01657843589782715,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=63488)"}} +{"tipo":"ajuste","dados":{"passo":62,"parametro":"pensamento_gate_g","de":0.0,"para":0.1,"motivo":"A-5 Bernstein empírico (LCB=0.0014 > 0, média=0.1469)"}} +{"tipo":"ajuste","dados":{"passo":63,"parametro":"pensamento_tau_c","de":0.01657843589782715,"para":0.016591370105743408,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=64512)"}} +{"tipo":"ajuste","dados":{"passo":63,"parametro":"pensamento_gate_g","de":0.1,"para":0.2,"motivo":"A-5 Bernstein empírico (LCB=0.0040 > 0, média=0.1472)"}} +{"tipo":"ajuste","dados":{"passo":64,"parametro":"pensamento_tau_c","de":0.016591370105743408,"para":0.016611099243164062,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=65536)"}} +{"tipo":"ajuste","dados":{"passo":64,"parametro":"pensamento_gate_g","de":0.2,"para":0.30000000000000004,"motivo":"A-5 Bernstein empírico (LCB=0.0064 > 0, média=0.1474)"}} +{"tipo":"ajuste","dados":{"passo":65,"parametro":"pensamento_tau_c","de":0.016611099243164062,"para":0.016618072986602783,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=66560)"}} +{"tipo":"ajuste","dados":{"passo":65,"parametro":"pensamento_gate_g","de":0.30000000000000004,"para":0.4,"motivo":"A-5 Bernstein empírico (LCB=0.0088 > 0, média=0.1478)"}} +{"tipo":"ajuste","dados":{"passo":66,"parametro":"pensamento_gate_g","de":0.4,"para":0.5,"motivo":"A-5 Bernstein empírico (LCB=0.0111 > 0, média=0.1479)"}} +{"tipo":"ajuste","dados":{"passo":67,"parametro":"pensamento_tau_c","de":0.016618072986602783,"para":0.01663726568222046,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=68608)"}} +{"tipo":"ajuste","dados":{"passo":67,"parametro":"pensamento_gate_g","de":0.5,"para":0.6,"motivo":"A-5 Bernstein empírico (LCB=0.0133 > 0, média=0.1480)"}} +{"tipo":"ajuste","dados":{"passo":68,"parametro":"pensamento_tau_c","de":0.01663726568222046,"para":0.016634821891784668,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=69632)"}} +{"tipo":"ajuste","dados":{"passo":68,"parametro":"pensamento_gate_g","de":0.6,"para":0.7,"motivo":"A-5 Bernstein empírico (LCB=0.0151 > 0, média=0.1479)"}} +{"tipo":"ajuste","dados":{"passo":69,"parametro":"pensamento_gate_g","de":0.7,"para":0.7999999999999999,"motivo":"A-5 Bernstein empírico (LCB=0.0172 > 0, média=0.1481)"}} +{"tipo":"ajuste","dados":{"passo":70,"parametro":"pensamento_gate_g","de":0.7999999999999999,"para":0.8999999999999999,"motivo":"A-5 Bernstein empírico (LCB=0.0190 > 0, média=0.1480)"}} +{"tipo":"ajuste","dados":{"passo":71,"parametro":"pensamento_tau_c","de":0.016634821891784668,"para":0.01662588119506836,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=72704)"}} +{"tipo":"ajuste","dados":{"passo":71,"parametro":"pensamento_gate_g","de":0.8999999999999999,"para":0.9999999999999999,"motivo":"A-5 Bernstein empírico (LCB=0.0208 > 0, média=0.1480)"}} +{"tipo":"ajuste","dados":{"passo":72,"parametro":"pensamento_tau_c","de":0.01662588119506836,"para":0.016639411449432373,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=73728)"}} +{"tipo":"ajuste","dados":{"passo":72,"parametro":"pensamento_gate_g","de":0.9999999999999999,"para":1.0,"motivo":"A-5 Bernstein empírico (LCB=0.0228 > 0, média=0.1481)"}} +{"tipo":"ajuste","dados":{"passo":73,"parametro":"pensamento_tau_c","de":0.016639411449432373,"para":0.01664215326309204,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=74752)"}} +{"tipo":"ajuste","dados":{"passo":74,"parametro":"pensamento_tau_c","de":0.01664215326309204,"para":0.016638875007629395,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=75776)"}} +{"tipo":"ajuste","dados":{"passo":75,"parametro":"pensamento_tau_c","de":0.016638875007629395,"para":0.01664435863494873,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=76800)"}} +{"tipo":"ajuste","dados":{"passo":76,"parametro":"pensamento_tau_c","de":0.01664435863494873,"para":0.01665741205215454,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=77824)"}} +{"tipo":"ajuste","dados":{"passo":77,"parametro":"pensamento_tau_c","de":0.01665741205215454,"para":0.016666710376739502,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=78848)"}} +{"tipo":"ajuste","dados":{"passo":78,"parametro":"pensamento_tau_c","de":0.016666710376739502,"para":0.016673803329467773,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=79872)"}} +{"tipo":"ajuste","dados":{"passo":79,"parametro":"pensamento_tau_c","de":0.016673803329467773,"para":0.016688168048858643,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=80896)"}} +{"tipo":"ajuste","dados":{"passo":80,"parametro":"pensamento_tau_c","de":0.016688168048858643,"para":0.016695618629455566,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=81920)"}} +{"tipo":"ajuste","dados":{"passo":81,"parametro":"pensamento_tau_c","de":0.016695618629455566,"para":0.016701817512512207,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=82944)"}} +{"tipo":"ajuste","dados":{"passo":82,"parametro":"pensamento_tau_c","de":0.016701817512512207,"para":0.01669764518737793,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=83968)"}} +{"tipo":"ajuste","dados":{"passo":83,"parametro":"pensamento_tau_c","de":0.01669764518737793,"para":0.0167122483253479,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=84992)"}} +{"tipo":"ajuste","dados":{"passo":84,"parametro":"pensamento_tau_c","de":0.0167122483253479,"para":0.01671600341796875,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=86016)"}} +{"tipo":"ajuste","dados":{"passo":85,"parametro":"pensamento_tau_c","de":0.01671600341796875,"para":0.01671910285949707,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=87040)"}} +{"tipo":"ajuste","dados":{"passo":86,"parametro":"pensamento_tau_c","de":0.01671910285949707,"para":0.016711294651031494,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=88064)"}} +{"tipo":"ajuste","dados":{"passo":87,"parametro":"pensamento_tau_c","de":0.016711294651031494,"para":0.016720473766326904,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=89088)"}} +{"tipo":"ajuste","dados":{"passo":88,"parametro":"pensamento_tau_c","de":0.016720473766326904,"para":0.01672607660293579,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=90112)"}} +{"tipo":"ajuste","dados":{"passo":89,"parametro":"pensamento_tau_c","de":0.01672607660293579,"para":0.016732096672058105,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=91136)"}} +{"tipo":"ajuste","dados":{"passo":90,"parametro":"pensamento_tau_c","de":0.016732096672058105,"para":0.01672607660293579,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=92160)"}} +{"tipo":"ajuste","dados":{"passo":91,"parametro":"pensamento_tau_c","de":0.01672607660293579,"para":0.016723573207855225,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=93184)"}} +{"tipo":"ajuste","dados":{"passo":92,"parametro":"pensamento_tau_c","de":0.016723573207855225,"para":0.016735374927520752,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=94208)"}} +{"tipo":"ajuste","dados":{"passo":93,"parametro":"pensamento_tau_c","de":0.016735374927520752,"para":0.016741812229156494,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=95232)"}} +{"tipo":"ajuste","dados":{"passo":94,"parametro":"pensamento_tau_c","de":0.016741812229156494,"para":0.016735613346099854,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=96256)"}} +{"tipo":"ajuste","dados":{"passo":95,"parametro":"pensamento_tau_c","de":0.016735613346099854,"para":0.016741573810577393,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=97280)"}} +{"tipo":"ajuste","dados":{"passo":98,"parametro":"pensamento_tau_c","de":0.016741573810577393,"para":0.01675236225128174,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=98304)"}} +{"tipo":"ajuste","dados":{"passo":99,"parametro":"pensamento_tau_c","de":0.01675236225128174,"para":0.016757845878601074,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=99328)"}} +{"tipo":"ajuste","dados":{"passo":100,"parametro":"pensamento_tau_c","de":0.016757845878601074,"para":0.016761422157287598,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=100352)"}} +{"tipo":"ajuste","dados":{"passo":101,"parametro":"pensamento_tau_c","de":0.016761422157287598,"para":0.01675891876220703,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=101376)"}} +{"tipo":"ajuste","dados":{"passo":102,"parametro":"pensamento_tau_c","de":0.01675891876220703,"para":0.01677107810974121,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=102400)"}} +{"tipo":"ajuste","dados":{"passo":104,"parametro":"pensamento_tau_c","de":0.01677107810974121,"para":0.016778409481048584,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=103424)"}} +{"tipo":"ajuste","dados":{"passo":105,"parametro":"pensamento_tau_c","de":0.016778409481048584,"para":0.016786634922027588,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=104448)"}} +{"tipo":"ajuste","dados":{"passo":106,"parametro":"pensamento_tau_c","de":0.016786634922027588,"para":0.016798019409179688,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=105472)"}} +{"tipo":"ajuste","dados":{"passo":107,"parametro":"pensamento_tau_c","de":0.016798019409179688,"para":0.0168115496635437,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=106496)"}} +{"tipo":"ajuste","dados":{"passo":108,"parametro":"pensamento_tau_c","de":0.0168115496635437,"para":0.016808629035949707,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=107520)"}} +{"tipo":"ajuste","dados":{"passo":109,"parametro":"pensamento_tau_c","de":0.016808629035949707,"para":0.016813039779663086,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=108544)"}} +{"tipo":"ajuste","dados":{"passo":110,"parametro":"pensamento_tau_c","de":0.016813039779663086,"para":0.016818881034851074,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=109568)"}} +{"tipo":"ajuste","dados":{"passo":112,"parametro":"pensamento_tau_c","de":0.016818881034851074,"para":0.016836047172546387,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=111616)"}} +{"tipo":"ajuste","dados":{"passo":113,"parametro":"pensamento_tau_c","de":0.016836047172546387,"para":0.016848087310791016,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=112640)"}} +{"tipo":"ajuste","dados":{"passo":114,"parametro":"pensamento_tau_c","de":0.016848087310791016,"para":0.01686161756515503,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=113664)"}} +{"tipo":"ajuste","dados":{"passo":115,"parametro":"pensamento_tau_c","de":0.01686161756515503,"para":0.0168687105178833,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=114688)"}} +{"tipo":"ajuste","dados":{"passo":116,"parametro":"pensamento_tau_c","de":0.0168687105178833,"para":0.016875267028808594,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=115712)"}} +{"tipo":"ajuste","dados":{"passo":117,"parametro":"pensamento_tau_c","de":0.016875267028808594,"para":0.016874253749847412,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=116736)"}} +{"tipo":"ajuste","dados":{"passo":118,"parametro":"pensamento_tau_c","de":0.016874253749847412,"para":0.0168837308883667,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=117760)"}} +{"tipo":"ajuste","dados":{"passo":119,"parametro":"pensamento_tau_c","de":0.0168837308883667,"para":0.01688694953918457,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=118784)"}} +{"tipo":"ajuste","dados":{"passo":120,"parametro":"pensamento_tau_c","de":0.01688694953918457,"para":0.016893506050109863,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=119808)"}} +{"tipo":"ajuste","dados":{"passo":122,"parametro":"pensamento_tau_c","de":0.016893506050109863,"para":0.016901671886444092,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=121856)"}} +{"tipo":"ajuste","dados":{"passo":123,"parametro":"pensamento_tau_c","de":0.016901671886444092,"para":0.016905546188354492,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=122880)"}} +{"tipo":"ajuste","dados":{"passo":124,"parametro":"pensamento_tau_c","de":0.016905546188354492,"para":0.016914963722229004,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=123904)"}} +{"tipo":"ajuste","dados":{"passo":125,"parametro":"pensamento_tau_c","de":0.016914963722229004,"para":0.01692676544189453,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=124928)"}} +{"tipo":"ajuste","dados":{"passo":126,"parametro":"pensamento_tau_c","de":0.01692676544189453,"para":0.01692938804626465,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=125952)"}} +{"tipo":"ajuste","dados":{"passo":127,"parametro":"pensamento_tau_c","de":0.01692938804626465,"para":0.01693546772003174,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=126976)"}} +{"tipo":"ajuste","dados":{"passo":128,"parametro":"pensamento_tau_c","de":0.01693546772003174,"para":0.016948997974395752,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=128000)"}} +{"tipo":"ajuste","dados":{"passo":129,"parametro":"pensamento_tau_c","de":0.016948997974395752,"para":0.016956567764282227,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=129024)"}} +{"tipo":"ajuste","dados":{"passo":130,"parametro":"pensamento_tau_c","de":0.016956567764282227,"para":0.016968727111816406,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=130048)"}} +{"tipo":"ajuste","dados":{"passo":134,"parametro":"pensamento_tau_c","de":0.016968727111816406,"para":0.01697641611099243,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=132096)"}} +{"tipo":"ajuste","dados":{"passo":137,"parametro":"pensamento_tau_c","de":0.01697641611099243,"para":0.016979634761810303,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=134144)"}} +{"tipo":"ajuste","dados":{"passo":138,"parametro":"pensamento_tau_c","de":0.016979634761810303,"para":0.016981422901153564,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=135168)"}} +{"tipo":"ajuste","dados":{"passo":139,"parametro":"pensamento_tau_c","de":0.016981422901153564,"para":0.01699155569076538,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=136192)"}} +{"tipo":"ajuste","dados":{"passo":140,"parametro":"pensamento_tau_c","de":0.01699155569076538,"para":0.0169944167137146,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=137216)"}} +{"tipo":"ajuste","dados":{"passo":141,"parametro":"pensamento_tau_c","de":0.0169944167137146,"para":0.017006099224090576,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=138240)"}} +{"tipo":"ajuste","dados":{"passo":142,"parametro":"pensamento_tau_c","de":0.017006099224090576,"para":0.017008602619171143,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=139264)"}} +{"tipo":"ajuste","dados":{"passo":143,"parametro":"pensamento_tau_c","de":0.017008602619171143,"para":0.017015516757965088,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=140288)"}} +{"tipo":"ajuste","dados":{"passo":145,"parametro":"pensamento_tau_c","de":0.017015516757965088,"para":0.017021477222442627,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=141312)"}} +{"tipo":"ajuste","dados":{"passo":146,"parametro":"pensamento_tau_c","de":0.017021477222442627,"para":0.01702880859375,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=142336)"}} +{"tipo":"ajuste","dados":{"passo":148,"parametro":"pensamento_tau_c","de":0.01702880859375,"para":0.01703542470932007,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=143360)"}} +{"tipo":"ajuste","dados":{"passo":149,"parametro":"pensamento_tau_c","de":0.01703542470932007,"para":0.017037272453308105,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=144384)"}} +{"tipo":"ajuste","dados":{"passo":151,"parametro":"pensamento_tau_c","de":0.017037272453308105,"para":0.017041385173797607,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=145408)"}} +{"tipo":"ajuste","dados":{"passo":152,"parametro":"pensamento_tau_c","de":0.017041385173797607,"para":0.017049670219421387,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=146432)"}} +{"tipo":"ajuste","dados":{"passo":153,"parametro":"pensamento_tau_c","de":0.017049670219421387,"para":0.017046570777893066,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=147456)"}} +{"tipo":"ajuste","dados":{"passo":154,"parametro":"pensamento_tau_c","de":0.017046570777893066,"para":0.017054259777069092,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=148480)"}} +{"tipo":"ajuste","dados":{"passo":156,"parametro":"pensamento_tau_c","de":0.017054259777069092,"para":0.01706010103225708,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=149504)"}} +{"tipo":"ajuste","dados":{"passo":157,"parametro":"pensamento_tau_c","de":0.01706010103225708,"para":0.017068684101104736,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=150528)"}} +{"tipo":"ajuste","dados":{"passo":158,"parametro":"pensamento_tau_c","de":0.017068684101104736,"para":0.0170784592628479,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=151552)"}} +{"tipo":"ajuste","dados":{"passo":160,"parametro":"pensamento_tau_c","de":0.0170784592628479,"para":0.01708817481994629,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=152576)"}} +{"tipo":"ajuste","dados":{"passo":162,"parametro":"pensamento_tau_c","de":0.01708817481994629,"para":0.017090141773223877,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=153600)"}} +{"tipo":"ajuste","dados":{"passo":164,"parametro":"pensamento_tau_c","de":0.017090141773223877,"para":0.0170973539352417,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=154624)"}} +{"tipo":"ajuste","dados":{"passo":165,"parametro":"pensamento_tau_c","de":0.0170973539352417,"para":0.017104685306549072,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=155648)"}} +{"tipo":"ajuste","dados":{"passo":166,"parametro":"pensamento_tau_c","de":0.017104685306549072,"para":0.017107903957366943,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=156672)"}} +{"tipo":"ajuste","dados":{"passo":167,"parametro":"pensamento_tau_c","de":0.017107903957366943,"para":0.017113983631134033,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=157696)"}} +{"tipo":"ajuste","dados":{"passo":168,"parametro":"pensamento_tau_c","de":0.017113983631134033,"para":0.017116248607635498,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=158720)"}} +{"tipo":"ajuste","dados":{"passo":170,"parametro":"pensamento_tau_c","de":0.017116248607635498,"para":0.017122328281402588,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=159744)"}} +{"tipo":"ajuste","dados":{"passo":171,"parametro":"pensamento_tau_c","de":0.017122328281402588,"para":0.017128288745880127,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=160768)"}} +{"tipo":"ajuste","dados":{"passo":172,"parametro":"pensamento_tau_c","de":0.017128288745880127,"para":0.017133712768554688,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=161792)"}} +{"tipo":"ajuste","dados":{"passo":173,"parametro":"pensamento_tau_c","de":0.017133712768554688,"para":0.017142534255981445,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=162816)"}} +{"tipo":"ajuste","dados":{"passo":174,"parametro":"pensamento_tau_c","de":0.017142534255981445,"para":0.017145395278930664,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=163840)"}} +{"tipo":"ajuste","dados":{"passo":175,"parametro":"pensamento_tau_c","de":0.017145395278930664,"para":0.01715296506881714,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=164864)"}} +{"tipo":"ajuste","dados":{"passo":176,"parametro":"pensamento_tau_c","de":0.01715296506881714,"para":0.01715928316116333,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=165888)"}} +{"tipo":"ajuste","dados":{"passo":179,"parametro":"pensamento_tau_c","de":0.01715928316116333,"para":0.0171622633934021,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=166912)"}} +{"tipo":"dump","dados":{"acordo_tentativas":[[0,0.5028600002309153],[1,0.4950322823521371],[2,0.4798875816486543],[3,0.4858313038324316],[4,0.4936330304990406],[5,0.48478373412429693],[6,0.5102738069156961],[7,0.495097345643444],[8,0.5009391059769163],[9,0.5067074737938432],[10,0.500634032379215],[11,0.4896627690953513],[12,0.49489349219948053],[13,0.5049056810579108],[14,0.5087293806524636],[15,0.5105456787277944],[16,0.5218364807563679],[17,0.5271108039014507],[18,0.5000031566790616],[19,0.5037049457702475],[20,0.4917097393918084],[21,0.4977927643897904],[22,0.5092931723047514],[23,0.48343566594606574],[24,0.4780086800213515],[25,0.5014162051957101],[26,0.4959500899810034],[27,0.5226301547275701],[28,0.5140384140880391],[29,0.5112525659399884],[30,0.5282249843755077],[31,0.4947323422044671],[32,0.5026412593967203],[33,0.5078339009002472],[34,0.5033388147906711],[35,0.49501056362836],[36,0.5126274524227483],[37,0.5064064586549648],[38,0.509220097194581],[39,0.4843062834115699],[40,0.48344614257803187],[41,0.5076655454662008],[42,0.501095445196673],[43,0.5232622742769308],[44,0.5078513798847174],[45,0.5061007815044528],[46,0.5147967577019396],[47,0.5136789209209383],[48,0.511277187052959],[49,0.49925600617643795],[50,0.4928130326346339],[51,0.5288023574006123],[52,0.49586504269124515],[53,0.5024730978402658],[54,0.501211090372332],[55,0.515379966624702],[56,0.49900681593377766],[58,0.5057968174611839],[59,0.5093751461924209],[60,0.5067863393038957],[61,0.5003585508820834],[62,0.4947806589237492],[63,0.5174476473863857],[64,0.49470607010395423],[65,0.5107765162247233],[66,0.4947440256655682],[67,0.5010196952110467],[68,0.49608979225255706],[69,0.5051402508033789],[70,0.5137791107263183],[71,0.5181295310903806],[72,0.5192325501363181],[73,0.49485721812622313],[74,0.48922642430625274],[75,0.5143582477467135],[76,0.5032481459590296],[77,0.49706912707188167],[78,0.49314710880328977],[79,0.4862163895838118],[80,0.4945040759630501],[81,0.5050333777338286],[82,0.5049005637799079],[83,0.4902614629342376],[84,0.48436840685705346],[85,0.5140893314480005],[86,0.5155901123944204],[87,0.5233347928927591],[88,0.5012184785058101],[89,0.4908947451040149],[90,0.5100736886815866],[91,0.4889509727800032],[92,0.5040305781682642],[93,0.5051442451173594],[94,0.5219239644162977],[95,0.5137179221977325],[98,0.4977410391923816],[99,0.51080007784185],[100,0.5047323306037773],[101,0.5165671084056763],[102,0.5061268517941547],[104,0.506462094160573],[105,0.5156913814425934],[106,0.49386223500672105],[107,0.5122764733290145],[108,0.5107483759250803],[109,0.5228661045548506],[110,0.510680131187352],[111,0.5108237865836903],[112,0.5198076062758142],[113,0.5065654440938184],[114,0.5054116566946808],[115,0.49691915108511847],[116,0.4982414208401072],[117,0.5000035631625602],[118,0.492798294503397],[119,0.49849974115689594],[120,0.5035712090320885],[121,0.5118594358597571],[122,0.5262012491839414],[123,0.5036681105072299],[124,0.5082135452394141],[125,0.5122260597418062],[126,0.5094843112650173],[127,0.5100657866181185],[128,0.510991378538165],[129,0.5094215934126017],[130,0.5002903601174088],[132,0.486712965250869],[134,0.5094546760374215],[136,0.5088323370049087],[137,0.4871564242785098],[138,0.5067987435031682],[139,0.4897583644584908],[140,0.4905160826650293],[141,0.48622328873413306],[142,0.5037654667321476],[143,0.4887495086780594],[145,0.504955483134836],[146,0.5013548591911482],[148,0.5044902188140744],[149,0.5191630411718506],[151,0.5006109667010605],[152,0.5045971653986877],[153,0.4956807148021956],[154,0.5053270674155405],[156,0.5141158875388404],[157,0.5150347594365788],[158,0.5146901643214127],[160,0.5054877389823863],[162,0.4907115531192782],[164,0.48217524548090296],[165,0.5022985541339343],[166,0.5111773851846616],[167,0.5081046163880577],[168,0.49784239246703993],[170,0.4983166469367764],[171,0.49231743154329405],[172,0.4973643472355131],[173,0.5257275487116809],[174,0.5086705264887618],[175,0.4988940144733836],[176,0.5107558603243282],[179,0.48784584764265065]],"ce_gatilhada_com":[[0,3.990682522455851],[1,4.172070026397705],[2,3.9922971725463867],[3,4.024745285511017],[4,4.040532827377319],[5,3.9871789614359536],[6,3.901935895284017],[7,4.017208576202393],[8,3.9832544922828674],[9,3.995508829752604],[10,4.048591196537018],[11,4.054214318593343],[12,4.201066335042317],[13,3.852038542429606],[14,4.049705346425374],[15,4.025229334831238],[16,4.368491172790527],[17,4.018544673919678],[18,3.85960324605306],[19,4.031909942626953],[20,4.01624608039856],[21,4.042102694511414],[22,4.215875864028931],[23,3.86575984954834],[24,4.182933509349823],[25,3.900304079055786],[26,4.225833177566528],[27,4.212203184763591],[28,4.296268701553345],[29,3.779969811439514],[30,4.043873906135559],[31,4.446818113327026],[32,4.050361553827922],[33,3.8948398431142173],[34,4.043341716130574],[35,3.7278977235158286],[36,4.134139855702718],[37,4.081378757953644],[38,4.045528471469879],[39,4.0925867557525635],[40,4.220437208811442],[41,4.1184563636779785],[42,3.9894015789031982],[43,3.908494234085083],[44,3.9376860857009888],[45,4.0315806070963545],[46,4.037987470626831],[47,4.160836219787598],[48,3.9042978286743164],[49,4.360845327377319],[50,3.9469112157821655],[51,3.711232304573059],[52,4.006168365478516],[53,4.096292177836101],[54,3.984761118888855],[55,4.017864306767781],[56,4.275344053904216],[58,4.67264986038208],[59,3.9628067016601562],[60,4.173342227935791],[61,4.021025975545247],[62,3.877448260784149],[63,4.1110148429870605],[64,3.7457308769226074],[65,4.00639283657074],[66,4.014901320139567],[67,3.7936489582061768],[68,3.992401123046875],[69,4.0935810804367065],[70,4.29033621152242],[71,4.0560877323150635],[72,3.7011795043945312],[73,3.845238208770752],[74,4.254753351211548],[75,4.245847225189209],[76,4.057919025421143],[77,4.2678611278533936],[78,3.8418723344802856],[79,4.271665573120117],[80,4.192492604255676],[81,3.8606152534484863],[82,3.883606751759847],[83,3.9665369987487793],[84,4.130507588386536],[85,4.143632968266805],[86,4.178323566913605],[87,3.69917893409729],[88,4.259217858314514],[89,4.011404395103455],[90,3.9650556445121765],[91,4.109483599662781],[92,4.101954460144043],[93,4.351506233215332],[94,4.01691746711731],[95,3.7731308937072754],[98,4.640173435211182],[99,4.044714093208313],[100,3.9896199703216553],[101,4.109546264012654],[102,4.145634651184082],[104,4.106081008911133],[105,3.9364733695983887],[106,4.039921402931213],[107,3.739548683166504],[108,4.034179608027141],[109,4.102705001831055],[110,4.072922587394714],[111,4.171568393707275],[112,3.7461421489715576],[113,4.1238484382629395],[114,4.109546184539795],[115,4.035416960716248],[116,3.918515920639038],[117,4.144512335459392],[118,3.932486891746521],[119,3.9242016474405923],[120,4.145139217376709],[121,4.045098940531413],[122,3.9033401012420654],[123,3.9184993505477905],[124,4.327580451965332],[125,3.9230809211730957],[126,4.2412800788879395],[127,4.00946569442749],[128,3.933870315551758],[129,4.072670221328735],[130,3.8194403648376465],[132,4.017874240875244],[134,4.273417949676514],[136,4.176693757375081],[137,4.235759496688843],[138,3.985278765360514],[139,3.7222108840942383],[140,4.34234356880188],[141,4.404490947723389],[142,4.373222351074219],[143,4.517516613006592],[145,4.281197786331177],[146,4.353374004364014],[148,4.205049514770508],[149,4.130898594856262],[151,4.001789887746175],[152,4.291512489318848],[153,3.964293599128723],[154,3.9655206203460693],[156,4.505177974700928],[157,4.049394130706787],[158,4.10487174987793],[160,3.9899709224700928],[162,3.9660974740982056],[164,3.7900190353393555],[165,4.037952899932861],[166,3.996012528737386],[167,4.371770858764648],[168,4.122939586639404],[170,4.000663995742798],[171,3.9514858722686768],[172,3.932365655899048],[173,4.118404865264893],[174,4.402906179428101],[175,3.99057936668396],[176,4.321465492248535],[179,4.181258678436279]],"ce_gatilhada_sem":[[0,4.101917902628581],[1,4.163182338078816],[2,3.9917688369750977],[3,4.061686992645264],[4,3.914612134297689],[5,3.9954094092051187],[6,3.911052703857422],[7,4.033169865608215],[8,4.016918420791626],[9,3.874128739039103],[10,4.0294270515441895],[11,3.9873576164245605],[12,4.171914180119832],[13,3.8939428329467773],[14,4.151257197062175],[15,3.9584625959396362],[16,4.402843952178955],[17,3.9543723265329995],[18,3.7655808130900064],[19,3.995170513788859],[20,4.079830288887024],[21,4.120199203491211],[22,4.174338638782501],[23,3.6481034755706787],[24,4.179996907711029],[25,3.8647427558898926],[26,4.139061689376831],[27,4.2459917068481445],[28,4.211869120597839],[29,3.7684987783432007],[30,4.1127729415893555],[31,4.477316379547119],[32,3.9516282081604004],[33,3.896077315012614],[34,3.938241720199585],[35,3.6764734586079917],[36,4.055819829305013],[37,4.0684592723846436],[38,3.9777721166610718],[39,4.062942206859589],[40,4.3250861167907715],[41,4.116140365600586],[42,4.0153621435165405],[43,3.8735488255818686],[44,3.9291712045669556],[45,4.013108730316162],[46,3.8660807609558105],[47,4.211712121963501],[48,3.9453582763671875],[49,4.298929929733276],[50,3.8404853343963623],[51,3.660251498222351],[52,4.019053141276042],[53,4.172148704528809],[54,4.147598385810852],[55,4.028499603271484],[56,4.277413527170817],[58,4.535336971282959],[59,3.892958641052246],[60,4.298323631286621],[61,4.021728992462158],[62,3.8642889261245728],[63,4.216510772705078],[64,3.652679681777954],[65,3.9728922843933105],[66,3.984992424647013],[67,3.8643219470977783],[68,4.017503420511882],[69,3.955687165260315],[70,4.221375862757365],[71,4.024052202701569],[72,3.7316792011260986],[73,3.7983953952789307],[74,4.257179856300354],[75,4.278022050857544],[76,4.021566867828369],[77,4.28641152381897],[78,3.969394564628601],[79,4.215531349182129],[80,4.160964012145996],[81,3.792202949523926],[82,3.9725190003712973],[83,3.934025764465332],[84,3.979703664779663],[85,4.03809928894043],[86,4.1925161480903625],[87,3.682358980178833],[88,4.250956296920776],[89,4.033212065696716],[90,3.8874682188034058],[91,4.179266810417175],[92,4.154380798339844],[93,4.3096184730529785],[94,3.988636791706085],[95,3.7214863300323486],[98,4.707629203796387],[99,4.0041927099227905],[100,3.8919597466786704],[101,4.103201866149902],[102,4.155486583709717],[104,4.151379108428955],[105,3.86596417427063],[106,3.944948434829712],[107,3.703117609024048],[108,3.9001522064208984],[109,4.073512315750122],[110,4.1418023109436035],[111,4.192521731058757],[112,3.5740835666656494],[113,4.0821919441223145],[114,4.167129993438721],[115,4.071088671684265],[116,3.785374402999878],[117,4.077940940856934],[118,3.9024194478988647],[119,3.871340831120809],[120,4.138059616088867],[121,4.075603087743123],[122,3.9188358783721924],[123,3.818215250968933],[124,4.307414531707764],[125,3.7961649894714355],[126,4.283974965413411],[127,4.004149556159973],[128,3.859858512878418],[129,4.08201789855957],[130,3.8480067253112793],[132,4.0343849658966064],[134,4.466161727905273],[136,4.151505629221599],[137,4.296725511550903],[138,3.970802386601766],[139,3.5077567100524902],[140,4.3825390338897705],[141,4.433453559875488],[142,4.3604656457901],[143,4.707491874694824],[145,4.377482891082764],[146,4.278686046600342],[148,4.204703092575073],[149,4.106816053390503],[151,4.095471064249675],[152,4.223231196403503],[153,3.9363183975219727],[154,3.9086225032806396],[156,4.421633720397949],[157,4.111346244812012],[158,4.1443915367126465],[160,4.035621643066406],[162,4.007079482078552],[164,4.060975551605225],[165,3.87597918510437],[166,3.9723476568857827],[167,4.324607849121094],[168,4.129934310913086],[170,4.045195460319519],[171,3.892066240310669],[172,3.898447275161743],[173,4.213126182556152],[174,4.3643341064453125],[175,3.981333017349243],[176,4.308775424957275],[179,4.270479917526245]],"confianca_media":[[0,0.016349429672118276],[1,0.01628106931457296],[2,0.016283084405586123],[3,0.016030373983085155],[4,0.016135476995259523],[5,0.0161596552352421],[6,0.01616502058459446],[7,0.016049514815676957],[8,0.015170336118899286],[9,0.015576519363094121],[10,0.01490299345459789],[11,0.015126589918509126],[12,0.015850006137043238],[13,0.015123668825253844],[14,0.015384220983833075],[15,0.01610579394036904],[16,0.01637979206861928],[17,0.01619737717555836],[18,0.015365263679996133],[19,0.015952367859426886],[20,0.016095003520604223],[21,0.01646821858594194],[22,0.015387677471153438],[23,0.016529641288798302],[24,0.015287903079297394],[25,0.016856397967785597],[26,0.01672379410592839],[27,0.01577658182941377],[28,0.016363478964194655],[29,0.016341496724635363],[30,0.015957053226884454],[31,0.01633789559127763],[32,0.015688272251281887],[33,0.016318523441441357],[34,0.01602209178963676],[35,0.01623544399626553],[36,0.01594467827817425],[37,0.015589922084473073],[38,0.015975633345078677],[39,0.01513460004935041],[40,0.01619111152831465],[41,0.01620461995480582],[42,0.016414244717452675],[43,0.016518934804480523],[44,0.01615689683239907],[45,0.015800899651367217],[46,0.016716221696697176],[47,0.016262758406810462],[48,0.016544767015147954],[49,0.01649336627451703],[50,0.01659804763039574],[51,0.0169804286560975],[52,0.016434288234449923],[53,0.016217988857533783],[54,0.016727703507058322],[55,0.015984096738975495],[56,0.016124231973662972],[58,0.017021609121002257],[59,0.01679999742191285],[60,0.01678759272908792],[61,0.01616502460092306],[62,0.014967053895816207],[63,0.01672939321724698],[64,0.017183856281917542],[65,0.01664112036814913],[66,0.01615300861885771],[67,0.01716067997040227],[68,0.015827911265660077],[69,0.016389922646339983],[70,0.016279231756925583],[71,0.015545766509603709],[72,0.016549582942388952],[73,0.016542578057851642],[74,0.016069239762146026],[75,0.01654030941426754],[76,0.016862142307218164],[77,0.016727928130421788],[78,0.01696282037300989],[79,0.01704749691998586],[80,0.01655264780856669],[81,0.01666478719562292],[82,0.016020216513425112],[83,0.017135745962150395],[84,0.016710361931473017],[85,0.01644541247515008],[86,0.015909937908872962],[87,0.016872288659214973],[88,0.016676127910614014],[89,0.01680906437104568],[90,0.01565126469358802],[91,0.016157687176018953],[92,0.017024302971549332],[93,0.01684855535859242],[94,0.016013496671803296],[95,0.017158931703306735],[98,0.01718986447667703],[99,0.016385928902309388],[100,0.016633932769764215],[101,0.01588103489484638],[102,0.0169947009999305],[104,0.01677281962474808],[105,0.016959616099484265],[106,0.017686676466837525],[107,0.017240363347809762],[108,0.016270359454210848],[109,0.016751181858126074],[110,0.016628622950520366],[111,0.016358657856471837],[112,0.017512602207716554],[113,0.01730180490994826],[114,0.017188897414598614],[115,0.017095242103096098],[116,0.016869035025592893],[117,0.016642501403111964],[118,0.017533648817334324],[119,0.016824906168039888],[120,0.01702570082852617],[121,0.016659228364005685],[122,0.01723920105723664],[123,0.016811317531391978],[124,0.01744122593663633],[125,0.01760043081594631],[126,0.016951985249761492],[127,0.016996074991766363],[128,0.01760607148753479],[129,0.017264852940570563],[130,0.01759291219059378],[132,0.01640245970338583],[134,0.017071489652153105],[136,0.016642238246276975],[137,0.01670463097980246],[138,0.016744249616749585],[139,0.017222756054252386],[140,0.016873570159077644],[141,0.01777230022707954],[142,0.01683571795001626],[143,0.017129913030657917],[145,0.016807068488560617],[146,0.017224559793248773],[148,0.017674043192528188],[149,0.016632848361041397],[151,0.017253212921787053],[152,0.017682694597169757],[153,0.016560303862206638],[154,0.017243098351173103],[156,0.017067693930584937],[157,0.017267903371248394],[158,0.017443765536881983],[160,0.01751318317838013],[162,0.016633108665701002],[164,0.017536596162244678],[165,0.01758150855312124],[166,0.017316454148385674],[167,0.017494912550318986],[168,0.016769614536315203],[170,0.01726531208259985],[171,0.0173232433735393],[172,0.0171601987676695],[173,0.01753415778512135],[174,0.016623332223389298],[175,0.017054743599146605],[176,0.017190517100971192],[179,0.016849814797751606]],"confianca_pos_correcao":[[0,0.1587987095117569],[1,0.1663273274898529],[2,0.17912134528160095],[3,0.1631140410900116],[4,0.1701049655675888],[5,0.16917672753334045],[6,0.14841961860656738],[7,0.17517447471618652],[8,0.1600557416677475],[9,0.16052547097206116],[10,0.14431972801685333],[11,0.1633056402206421],[12,0.1525920331478119],[13,0.15926600992679596],[14,0.16420531272888184],[15,0.16214346885681152],[16,0.18704496324062347],[17,0.16519415378570557],[18,0.15611524879932404],[19,0.17925547063350677],[20,0.15833494067192078],[21,0.1518106460571289],[22,0.168626070022583],[23,0.1704416424036026],[24,0.16131165623664856],[25,0.18132872879505157],[26,0.18253028392791748],[27,0.14700543880462646],[28,0.15818580985069275],[29,0.17825765907764435],[30,0.1553850919008255],[31,0.1577756702899933],[32,0.14996713399887085],[33,0.1858246624469757],[34,0.14773237705230713],[35,0.14630520343780518],[36,0.14721813797950745],[37,0.1610732227563858],[38,0.1230509951710701],[39,0.14974986016750336],[40,0.16982029378414154],[41,0.16396604478359222],[42,0.16920259594917297],[43,0.1485423445701599],[44,0.16894589364528656],[45,0.14777806401252747],[46,0.16185319423675537],[47,0.17267826199531555],[48,0.16693347692489624],[49,0.18061144649982452],[50,0.15225984156131744],[51,0.18312600255012512],[52,0.16770082712173462],[53,0.14962968230247498],[54,0.17909981310367584],[55,0.16141952574253082],[56,0.17458635568618774],[58,0.17352823913097382],[59,0.16666799783706665],[60,0.18238267302513123],[61,0.16688455641269684],[62,0.13234135508537292],[63,0.18437083065509796],[64,0.17498065531253815],[65,0.18873770534992218],[66,0.1708756983280182],[67,0.17525817453861237],[68,0.1554616391658783],[69,0.17447271943092346],[70,0.1566036343574524],[71,0.16322262585163116],[72,0.17739078402519226],[73,0.16943442821502686],[74,0.161244198679924],[75,0.1959790587425232],[76,0.1794375628232956],[77,0.165488138794899],[78,0.17656467854976654],[79,0.16869157552719116],[80,0.16616564989089966],[81,0.18107832968235016],[82,0.17148537933826447],[83,0.18580180406570435],[84,0.17943456768989563],[85,0.1608690619468689],[86,0.1618165373802185],[87,0.18248027563095093],[88,0.19001397490501404],[89,0.15740828216075897],[90,0.15138156712055206],[91,0.1554187834262848],[92,0.1856810301542282],[93,0.17465457320213318],[94,0.1631956696510315],[95,0.17258146405220032],[98,0.17971810698509216],[99,0.16570588946342468],[100,0.1825055181980133],[101,0.16742363572120667],[102,0.1881554126739502],[104,0.1745765656232834],[105,0.1879185289144516],[106,0.18023289740085602],[107,0.18222127854824066],[108,0.1607009470462799],[109,0.17419284582138062],[110,0.17272350192070007],[111,0.1578918993473053],[112,0.1821414679288864],[113,0.1861209273338318],[114,0.18036682903766632],[115,0.18187958002090454],[116,0.19053493440151215],[117,0.1654602289199829],[118,0.19134759902954102],[119,0.16087384521961212],[120,0.18222679197788239],[121,0.1776803433895111],[122,0.1786060482263565],[123,0.1691613793373108],[124,0.19052045047283173],[125,0.19370625913143158],[126,0.17165188491344452],[127,0.17674757540225983],[128,0.19645051658153534],[129,0.20197993516921997],[130,0.20191556215286255],[132,0.18772654235363007],[134,0.18445192277431488],[136,0.17469234764575958],[137,0.1677069514989853],[138,0.16896817088127136],[139,0.17658907175064087],[140,0.1745469719171524],[141,0.19866693019866943],[142,0.18142516911029816],[143,0.175930455327034],[145,0.19531768560409546],[146,0.1821127086877823],[148,0.18900153040885925],[149,0.18137390911579132],[151,0.17204469442367554],[152,0.18441344797611237],[153,0.15157270431518555],[154,0.18329522013664246],[156,0.18380318582057953],[157,0.17717640101909637],[158,0.2028713822364807],[160,0.18476104736328125],[162,0.18993763625621796],[164,0.18922974169254303],[165,0.1929178386926651],[166,0.19783638417720795],[167,0.18783369660377502],[168,0.1825595498085022],[170,0.1676768958568573],[171,0.17470483481884003],[172,0.16972526907920837],[173,0.17687201499938965],[174,0.1784033328294754],[175,0.18470066785812378],[176,0.1751042753458023],[179,0.18028345704078674]],"ece_calibracao":[[0,0.11367493326542899]],"ece_pos_calibracao":[[0,0.04155296389944852]],"escore_aprendizado":[[0,0.0],[25,0.1875],[50,0.1875],[75,0.19861775566605808],[100,0.1789015181673504],[125,0.17339257615937123],[150,0.1955498766240361],[175,0.18667495139451393],[179,0.20192427652934322]],"lr":[[0,0.000011249999999999999],[1,0.000011249999999999999],[2,0.000011249999999999999],[3,0.000011249999999999999],[4,0.000011249999999999999],[5,0.000011249999999999999],[6,0.000011249999999999999],[7,0.000011249999999999999],[8,0.000011249999999999999],[9,0.000011249999999999999],[10,0.000011249999999999999],[11,0.000011249999999999999],[12,0.000011249999999999999],[13,0.000011249999999999999],[14,0.000011249999999999999],[15,0.000011249999999999999],[16,0.000011249999999999999],[17,0.000011249999999999999],[18,0.000011249999999999999],[19,0.000011249999999999999],[20,0.000011249999999999999],[21,0.000011249999999999999],[22,0.000011249999999999999],[23,0.000011249999999999999],[24,0.000011249999999999999],[25,0.000011249999999999999],[26,0.000011249999999999999],[27,0.000011249999999999999],[28,0.000011249999999999999],[29,0.000011249999999999999],[30,0.000011249999999999999],[31,0.000011249999999999999],[32,0.000011249999999999999],[33,0.000011249999999999999],[34,0.000011249999999999999],[35,0.000011249999999999999],[36,0.000011249999999999999],[37,0.000011249999999999999],[38,0.000011249999999999999],[39,0.000011249999999999999],[40,0.000011249999999999999],[41,0.000011249999999999999],[42,0.000011249999999999999],[43,0.000011249999999999999],[44,0.000011249999999999999],[45,0.000011249999999999999],[46,0.000011249999999999999],[47,0.000011249999999999999],[48,0.000011249999999999999],[49,0.000011249999999999999],[50,0.000011249999999999999],[51,0.000011249999999999999],[52,0.000011249999999999999],[53,0.000011249999999999999],[54,0.000011249999999999999],[55,0.000011249999999999999],[56,0.000011249999999999999],[57,0.000011249999999999999],[58,0.000011249999999999999],[59,0.000011249999999999999],[60,0.000011249999999999999],[61,0.000011249999999999999],[62,0.000011249999999999999],[63,0.000011249999999999999],[64,0.000011249999999999999],[65,0.000011249999999999999],[66,0.000011249999999999999],[67,0.000011249999999999999],[68,0.000011249999999999999],[69,0.000011249999999999999],[70,0.000011249999999999999],[71,0.000011249999999999999],[72,0.000011249999999999999],[73,0.000011249999999999999],[74,0.000011249999999999999],[75,0.000011249999999999999],[76,0.000011249999999999999],[77,0.000011249999999999999],[78,0.000011249999999999999],[79,0.000011249999999999999],[80,0.000011249999999999999],[81,0.000011249999999999999],[82,0.000011249999999999999],[83,0.000011249999999999999],[84,0.000011249999999999999],[85,0.000011249999999999999],[86,0.000011249999999999999],[87,0.000011249999999999999],[88,0.000011249999999999999],[89,0.000011249999999999999],[90,0.000011249999999999999],[91,0.000011249999999999999],[92,0.000011249999999999999],[93,0.000011249999999999999],[94,0.000011249999999999999],[95,0.000011249999999999999],[96,0.000011249999999999999],[97,0.000011249999999999999],[98,0.000011249999999999999],[99,0.000011249999999999999],[100,0.000011249999999999999],[101,0.000011249999999999999],[102,0.000011249999999999999],[103,0.000011249999999999999],[104,0.000011249999999999999],[105,0.000011249999999999999],[106,0.000011249999999999999],[107,0.000011249999999999999],[108,0.000011249999999999999],[109,0.000011249999999999999],[110,0.000011249999999999999],[111,0.000011249999999999999],[112,0.000011249999999999999],[113,0.000011249999999999999],[114,0.000011249999999999999],[115,0.000011249999999999999],[116,0.000011249999999999999],[117,0.000011249999999999999],[118,0.000011249999999999999],[119,0.000011249999999999999],[120,0.000011249999999999999],[121,0.000011249999999999999],[122,0.000011249999999999999],[123,0.000011249999999999999],[124,0.000011249999999999999],[125,0.000011249999999999999],[126,0.000011249999999999999],[127,0.000011249999999999999],[128,0.000011249999999999999],[129,0.000011249999999999999],[130,0.000011249999999999999],[131,0.000011249999999999999],[132,0.000011249999999999999],[133,0.000011249999999999999],[134,0.000011249999999999999],[135,0.000011249999999999999],[136,0.000011249999999999999],[137,0.000011249999999999999],[138,0.000011249999999999999],[139,0.000011249999999999999],[140,0.000011249999999999999],[141,0.000011249999999999999],[142,0.000011249999999999999],[143,0.000011249999999999999],[144,0.000011249999999999999],[145,0.000011249999999999999],[146,0.000011249999999999999],[147,0.000011249999999999999],[148,0.000011249999999999999],[149,0.000011249999999999999],[150,0.000011249999999999999],[151,0.000011249999999999999],[152,0.000011249999999999999],[153,0.000011249999999999999],[154,0.000011249999999999999],[155,0.000011249999999999999],[156,0.000011249999999999999],[157,0.000011249999999999999],[158,0.000011249999999999999],[159,0.000011249999999999999],[160,0.000011249999999999999],[161,0.000011249999999999999],[162,0.000011249999999999999],[163,0.000011249999999999999],[164,0.000011249999999999999],[165,0.000011249999999999999],[166,0.000011249999999999999],[167,0.000011249999999999999],[168,0.000011249999999999999],[169,0.000011249999999999999],[170,0.000011249999999999999],[171,0.000011249999999999999],[172,0.000011249999999999999],[173,0.000011249999999999999],[174,0.000011249999999999999],[175,0.000011249999999999999],[176,0.000011249999999999999],[177,0.000011249999999999999],[178,0.000011249999999999999],[179,0.000011249999999999999]],"norma_delta":[[0,1.5881213106234895],[1,1.5187163926162026],[2,1.4676972532519672],[3,1.5344224879730126],[4,1.5860104097791976],[5,1.433515000843958],[6,1.473765959390385],[7,1.478253884372159],[8,1.4521736394238383],[9,1.507312909310302],[10,1.4530303267945637],[11,1.420800274476463],[12,1.5223447352823842],[13,1.5231012217747875],[14,1.4506522849038557],[15,1.414737940538519],[16,1.4384710929440312],[17,1.5391225270604851],[18,1.4168698595220457],[19,1.357180040072414],[20,1.4439511873179134],[21,1.4591488419944938],[22,1.43338742967291],[23,1.3670427954253046],[24,1.414136473686119],[25,1.4153083666107067],[26,1.3862391542469625],[27,1.4801708716909903],[28,1.3233048878134017],[29,1.4592565157576403],[30,1.4188916274309595],[31,1.336727154346936],[32,1.3984532275460964],[33,1.3608432163378776],[34,1.394215224758686],[35,1.3234003739627276],[36,1.2936490392682327],[37,1.3195586450690584],[38,1.3527493420135583],[39,1.3094055845926666],[40,1.338821376960053],[41,1.3585567861993955],[42,1.3778088230733765],[43,1.3238328579626406],[44,1.347021865803189],[45,1.3914350657662604],[46,1.3155799785531048],[47,1.3162802344433508],[48,1.3699135768389963],[49,1.339731817880561],[50,1.2411241598580742],[51,1.2192444409698728],[52,1.279440835322008],[53,1.2082798003253752],[54,1.328700086960625],[55,1.294848685979613],[56,1.2721077933252138],[58,1.3517597914451],[59,1.247227741721065],[60,1.2230872713799126],[61,1.2796265621371403],[62,1.1947759783024399],[63,1.2206058554996286],[64,1.2175647645522645],[65,1.193042169796082],[66,1.2479505427140298],[67,1.173009399669089],[68,1.2320705100355331],[69,1.1712692729785104],[70,1.2830161505683877],[71,1.205887731200803],[72,1.2398159011953394],[73,1.1971719278882653],[74,1.2607118350274948],[75,1.2853924844322975],[76,1.1853782370471935],[77,1.1418602408053997],[78,1.182272649043498],[79,1.1762471844176303],[80,1.216092856626294],[81,1.2228879861322488],[82,1.2214267874507894],[83,1.1900000105785722],[84,1.14475130953592],[85,1.1374170839041227],[86,1.2302429960935586],[87,1.169551121932147],[88,1.2054623280401737],[89,1.166737497541043],[90,1.1434503339916178],[91,1.0915031242657909],[92,1.1990896149234676],[93,1.2426560196120255],[94,1.2983554524964347],[95,1.2455282333157174],[98,1.1934021288116785],[99,1.178914859201837],[100,1.067260679358217],[101,1.1616057001659732],[102,1.1513711219053855],[104,1.221638216066434],[105,1.1251509353264255],[106,1.1331374129947858],[107,1.2007797469537347],[108,1.113963563867668],[109,1.215493620218272],[110,1.2421799665498516],[111,1.1749033137372877],[112,1.0471369287567998],[113,1.0969081515005572],[114,1.1423371933143778],[115,1.0768091215505446],[116,1.1655558281795044],[117,1.1567673427913285],[118,1.0500327354959456],[119,1.0040927223954677],[120,1.02617605010288],[121,1.0883687747502586],[122,1.0257829188197465],[123,1.0835483533265058],[124,1.168828437335468],[125,1.1202796345975004],[126,1.1129780138865442],[127,1.155153373096526],[128,1.1215102322639294],[129,1.1001367202236771],[130,1.108218436392535],[132,1.063895823326342],[134,1.0782330223920904],[136,1.015893812551505],[137,1.0501463152349244],[138,1.0829516741114045],[139,1.1098491568784763],[140,1.0869647320147047],[141,1.1049514476759563],[142,1.045328791880555],[143,1.0375462992804874],[145,1.1107967024589718],[146,1.034538343733939],[148,1.0768360227858174],[149,1.0458201887563598],[151,1.0746379242532846],[152,1.0320736456919322],[153,1.0655785447004094],[154,1.1212779036742258],[156,1.0618331162677959],[157,0.9926745803608082],[158,1.0321711270000715],[160,1.1032074354455075],[162,1.1197309551488877],[164,1.047206427648706],[165,1.0202195339874658],[166,1.0668857600290191],[167,1.0997551233649665],[168,1.0428433003505362],[170,1.0519360857864473],[171,1.0549136449174559],[172,1.0122361204289128],[173,1.0879914324367008],[174,0.9915395107543743],[175,1.0664416855734016],[176,1.0151726522319844],[179,1.011296069836927]],"norma_grad":[[0,1.1408509894666303],[1,1.1620085168212773],[2,1.0717859918857777],[3,0.9599823276242924],[4,1.0849271631996633],[5,1.046068254589276],[6,1.0972726294357853],[7,1.1782997893302891],[8,1.1724296450318092],[9,1.1342181850315487],[10,1.1596105914821584],[11,1.074006801666198],[12,1.1719202932917543],[13,1.238410597309227],[14,1.1567173953444683],[15,1.103070143394678],[16,0.9881221960448122],[17,1.0863905702819072],[18,1.2623275458302878],[19,1.0427515851464686],[20,0.9654524585568002],[21,0.9980506381745176],[22,1.0998335411597937],[23,1.2273590471875544],[24,1.1606332471933027],[25,1.0623696710347608],[26,1.0013156268885286],[27,1.15433658746776],[28,1.123854479223399],[29,1.0937723963502561],[30,1.0128954737379696],[31,1.1563576577084211],[32,1.0139426177988846],[33,1.040041358207631],[34,1.0953961899153484],[35,1.0276038466230804],[36,1.024496688481309],[37,1.1889800061075806],[38,1.2001040837718178],[39,1.0557312557170926],[40,1.2209234432518836],[41,1.1937919035716358],[42,1.104659246071382],[43,1.1048959292112999],[44,1.0982722841840904],[45,1.1157871715892738],[46,1.0977537824335946],[47,1.110290963635313],[48,1.014946366624604],[49,1.0679475056858099],[50,1.0047684710359144],[51,1.0637268619064597],[52,1.0760724249998768],[53,1.001403844669581],[54,1.1438561122558717],[55,1.1558197340202172],[56,1.1181234275691552],[57,2.143114624452434],[58,1.0724950702992064],[59,1.1675023199846155],[60,1.1515349124592495],[61,1.0272396694426715],[62,1.2352242883822042],[63,1.1387031557667886],[64,1.131338193555846],[65,1.1483496680539762],[66,1.047181105024617],[67,1.2109860572179512],[68,1.0062536395018291],[69,1.1014797109416863],[70,1.1007108248764754],[71,0.9576348940925878],[72,0.9721146195377273],[73,1.2029629161538964],[74,1.1352208810595206],[75,1.058138448172156],[76,1.0807239480178379],[77,1.02457448656393],[78,1.0726404156249443],[79,1.0702693541472654],[80,1.1489117154524593],[81,1.108072070426233],[82,1.1948931636625613],[83,1.1329279340076943],[84,1.0028653517409813],[85,1.1461768915597113],[86,1.1229880229845943],[87,1.0087857959782998],[88,1.0623247328279406],[89,0.9501125211764239],[90,1.0020594186251126],[91,1.0429475143796836],[92,1.135989304323665],[93,1.0721633595546796],[94,1.0547363265752603],[95,1.088800986610709],[96,2.2669286304172647],[97,2.3954033061071267],[98,1.1269234633961163],[99,1.0464108464605488],[100,1.0946585133188407],[101,1.0888350009896808],[102,1.0130571306772471],[103,2.0116712784948527],[104,1.0511956912906073],[105,1.091401599037621],[106,1.1245986167767974],[107,1.1410530765989884],[108,1.1036604814427728],[109,1.0111020790272762],[110,1.0800145915131298],[111,1.162890173030024],[112,0.9821830167750351],[113,1.0058008861239327],[114,1.0196685546693574],[115,1.0753382433189824],[116,1.172967399678557],[117,1.1099166653311547],[118,1.1027899485893229],[119,1.0744694652351066],[120,0.8888696102338669],[121,1.1156725462454988],[122,1.1216605598456653],[123,1.0606588953519689],[124,1.1018656325613787],[125,1.1877490977438965],[126,1.1378804564481921],[127,1.0914884564719853],[128,1.0313243143364672],[129,1.0950742031446645],[130,1.183261649867716],[131,1.974679591352598],[132,1.0875035660116001],[133,2.210752788121471],[134,1.1586115612938919],[135,1.8156598612687298],[136,1.0338708977376527],[137,1.1832502899848218],[138,1.0234304195054762],[139,1.0233816091253474],[140,1.083521231628433],[141,1.205081793516156],[142,1.1658930620288566],[143,1.11549829118145],[144,2.30350570372614],[145,1.3243929726231503],[146,1.0508834821378938],[147,2.0364198623930747],[148,1.1383850067557006],[149,1.0342079584192119],[150,1.7307760130679946],[151,1.1765056311177946],[152,1.0343096807189474],[153,1.18133846184018],[154,0.9634770545039816],[155,2.0937149093788983],[156,1.1582335618733566],[157,1.059747661317403],[158,1.2236411389983588],[159,1.958231556905546],[160,1.041767084559792],[161,2.0395926403140874],[162,1.1087352637330974],[163,2.04896552222598],[164,1.1238861217276406],[165,1.2499079725695277],[166,1.0911500003634453],[167,0.9467151839924393],[168,1.178811262782175],[169,2.177803870259314],[170,1.1256682955836608],[171,1.0670686738439257],[172,1.1159536174505167],[173,1.1515091290804427],[174,1.0784027666725535],[175,1.0988231519956964],[176,1.1095239628253828],[177,2.346264572125264],[178,2.00156131768225],[179,1.123615076102498]],"perda":[[0,3.9194343090057373],[1,4.087978363037109],[2,4.064610481262207],[3,3.9004688262939453],[4,3.937434196472168],[5,3.9801411628723145],[6,3.926401138305664],[7,4.046908378601074],[8,4.003106594085693],[9,4.005749225616455],[10,3.96597957611084],[11,3.932399272918701],[12,4.0554423332214355],[13,3.929011344909668],[14,4.005979537963867],[15,3.8883581161499023],[16,4.0282487869262695],[17,3.9987754821777344],[18,3.846325397491455],[19,4.034093379974365],[20,4.117684364318848],[21,3.998878002166748],[22,4.009410381317139],[23,3.9910614490509033],[24,4.081048011779785],[25,3.914362668991089],[26,4.144887447357178],[27,4.01850700378418],[28,4.124208450317383],[29,4.068880081176758],[30,3.9478023052215576],[31,3.958327293395996],[32,4.126319885253906],[33,3.8923895359039307],[34,3.914989948272705],[35,3.9405405521392822],[36,4.000115871429443],[37,3.901688814163208],[38,4.082619667053223],[39,4.0550665855407715],[40,4.009676456451416],[41,4.000525951385498],[42,3.904207229614258],[43,4.021977424621582],[44,4.004317283630371],[45,3.9284579753875732],[46,3.9724302291870117],[47,3.947613000869751],[48,4.022891044616699],[49,4.069355010986328],[50,3.942828893661499],[51,3.901921033859253],[52,3.9620864391326904],[53,3.844869613647461],[54,3.9835174083709717],[55,4.0107340812683105],[56,3.86012864112854],[57,3.9929594652993337],[58,3.891345977783203],[59,4.1436614990234375],[60,3.9580047130584717],[61,3.925525665283203],[62,3.8363969326019287],[63,3.8033995628356934],[64,3.9778149127960205],[65,4.09800910949707],[66,3.9001619815826416],[67,3.985206127166748],[68,3.966222047805786],[69,3.8126461505889893],[70,4.110163688659668],[71,3.93788480758667],[72,3.9434359073638916],[73,3.8149378299713135],[74,3.981889247894287],[75,3.910994052886963],[76,3.926023483276367],[77,4.031162738800049],[78,3.9504055976867676],[79,3.9473400115966797],[80,3.932459831237793],[81,3.757617473602295],[82,3.9626693725585938],[83,3.9926092624664307],[84,3.997727870941162],[85,3.930081844329834],[86,4.022006511688232],[87,4.031554698944092],[88,3.9477455615997314],[89,4.051133155822754],[90,3.9200427532196045],[91,3.995992660522461],[92,3.885881185531616],[93,4.14095401763916],[94,4.06342077255249],[95,4.0369157791137695],[96,4.053732088633946],[97,4.033645868301392],[98,4.02833890914917],[99,3.929987907409668],[100,4.058657646179199],[101,3.9100680351257324],[102,4.044567108154297],[103,3.933227675301688],[104,3.8929080963134766],[105,3.981990337371826],[106,4.086297512054443],[107,4.002747535705566],[108,3.890859603881836],[109,3.9656243324279785],[110,3.8666393756866455],[111,4.056665897369385],[112,3.969169855117798],[113,3.9328463077545166],[114,3.889716148376465],[115,4.07361364364624],[116,4.1161088943481445],[117,3.9725546836853027],[118,3.9570634365081787],[119,3.971015214920044],[120,3.9546022415161133],[121,4.1244964599609375],[122,4.082955837249756],[123,3.9142136573791504],[124,3.879279136657715],[125,3.9412806034088135],[126,3.9267473220825195],[127,4.085089683532715],[128,4.018453121185303],[129,4.126855850219727],[130,3.954861879348755],[131,3.998305218560355],[132,4.016144752502441],[133,4.098225695746286],[134,4.083544731140137],[135,4.037397589002337],[136,3.9111850261688232],[137,4.0095295906066895],[138,3.8264782428741455],[139,3.9539082050323486],[140,3.8899524211883545],[141,4.080811023712158],[142,3.980506658554077],[143,3.968421459197998],[144,3.8352764674595425],[145,3.8185315132141113],[146,3.9458961486816406],[147,3.9066452980041504],[148,3.8366189002990723],[149,3.867034912109375],[150,4.05700877734593],[151,3.8473520278930664],[152,4.081768035888672],[153,3.998035430908203],[154,3.8764076232910156],[155,3.997589315686907],[156,3.935149669647217],[157,4.169554233551025],[158,3.9677000045776367],[159,3.983267000743321],[160,4.053371906280518],[161,4.014926195144653],[162,3.933337450027466],[163,3.8815345764160156],[164,3.9069101810455322],[165,3.8870015144348145],[166,3.9478001594543457],[167,4.013628959655762],[168,3.9438586235046387],[169,4.112010206495013],[170,3.9096741676330566],[171,3.909297466278076],[172,3.896806478500366],[173,4.234826564788818],[174,3.8643367290496826],[175,3.9398603439331055],[176,4.009671211242676],[177,3.8983241489955356],[178,3.9342243671417236],[179,4.10706901550293]],"taxa_gatilho":[[0,0.75],[1,0.75],[2,0.5],[3,1.0],[4,0.75],[5,0.75],[6,0.75],[7,1.0],[8,1.0],[9,0.75],[10,1.0],[11,0.75],[12,0.75],[13,0.75],[14,0.75],[15,0.5],[16,0.25],[17,0.75],[18,0.75],[19,0.75],[20,0.5],[21,0.5],[22,1.0],[23,0.25],[24,1.0],[25,0.25],[26,0.5],[27,0.75],[28,0.5],[29,0.5],[30,0.5],[31,0.5],[32,0.75],[33,0.75],[34,0.75],[35,0.75],[36,0.75],[37,1.0],[38,1.0],[39,1.0],[40,0.75],[41,0.5],[42,0.5],[43,0.75],[44,0.5],[45,0.75],[46,0.5],[47,0.5],[48,0.25],[49,0.5],[50,0.5],[51,0.5],[52,0.75],[53,0.75],[54,0.5],[55,0.75],[56,0.75],[57,0.0],[58,0.25],[59,0.25],[60,0.25],[61,0.75],[62,1.0],[63,0.25],[64,0.25],[65,0.5],[66,0.75],[67,0.25],[68,0.75],[69,0.5],[70,0.75],[71,1.0],[72,0.25],[73,0.75],[74,1.0],[75,0.5],[76,0.25],[77,0.5],[78,0.5],[79,0.25],[80,0.5],[81,0.75],[82,0.75],[83,0.25],[84,0.5],[85,0.75],[86,1.0],[87,0.25],[88,0.5],[89,0.5],[90,1.0],[91,1.0],[92,0.25],[93,0.5],[94,1.0],[95,0.5],[96,0.0],[97,0.0],[98,0.25],[99,0.5],[100,0.75],[101,0.75],[102,0.25],[103,0.0],[104,0.25],[105,0.5],[106,0.5],[107,0.25],[108,0.75],[109,0.5],[110,0.5],[111,0.75],[112,0.25],[113,0.25],[114,0.25],[115,0.5],[116,0.25],[117,0.75],[118,0.5],[119,0.75],[120,0.5],[121,0.75],[122,0.5],[123,0.5],[124,0.25],[125,0.25],[126,0.75],[127,0.5],[128,0.25],[129,0.5],[130,0.25],[131,0.0],[132,0.75],[133,0.0],[134,0.25],[135,0.0],[136,0.75],[137,0.5],[138,0.75],[139,0.25],[140,0.5],[141,0.25],[142,0.5],[143,0.25],[144,0.0],[145,0.5],[146,0.5],[147,0.0],[148,0.5],[149,0.5],[150,0.0],[151,0.75],[152,0.5],[153,1.0],[154,0.25],[155,0.0],[156,0.25],[157,0.25],[158,0.25],[159,0.0],[160,0.25],[161,0.0],[162,0.5],[163,0.0],[164,0.25],[165,0.25],[166,0.75],[167,0.25],[168,0.5],[169,0.0],[170,0.5],[171,0.25],[172,0.5],[173,0.25],[174,0.5],[175,0.25],[176,0.25],[177,0.0],[178,0.0],[179,0.5]],"temperatura_calibrada":[[0,0.28844055695274645]]}} diff --git a/resultados/telemetria_v010.json b/resultados/telemetria_v010.json new file mode 100644 index 0000000000000000000000000000000000000000..c9470eb813cf0308c6eda8859f0080cd63b7c8b1 --- /dev/null +++ b/resultados/telemetria_v010.json @@ -0,0 +1,155 @@ +{ + "versao": "0.10.0", + "estrutura": { + "arquivos_fonte": 61, + "maior_arquivo": { + "caminho": "src/fnh.rs", + "bytes": 57580 + }, + "monolitos_v090_bytes": { + "src/python.rs": 112410, + "src/jepa.rs": 58407, + "src/pensamento.rs": 53408, + "src/moe_redirecionavel.rs": 50941 + }, + "subdirs": [ + "src/jepa", + "src/moe_redirecionavel", + "src/pensamento", + "src/python" + ], + "arquivos_por_diretorio": { + "src": 31, + "src/jepa": 8, + "src/moe_redirecionavel": 6, + "src/pensamento": 5, + "src/python": 11 + } + }, + "compilacao": { + "frio_dominio_s": 68, + "frio_completo_s": 86, + "frio_dominio_v090_s": 70, + "frio_completo_v090_s": 85, + "overhead_python_pct": 20.9, + "incremental_toque_s": { + "v090_jepa_rs_monolito": 1.06, + "v010_jepa_variantes": 1.13, + "v090_pensamento_rs_monolito": 1.13, + "v010_pensamento_motor": 1.15, + "v090_moe_rs_monolito": 1.05, + "v010_moe_nucleo": 1.2, + "controle_tipos_rs_v090": 1.21, + "controle_tipos_rs_v010": 1.19 + }, + "nota": "a alegação O(N/k) da crítica 1 é REFUTADA em granularidade de arquivo: o custo incremental (~1,1s) é dominado pelo overhead fixo do rustc/cargo, não pelo tamanho do arquivo tocado (controle tipos.rs varia igual). O ganho comprovado da divisão é SRP/manutenibilidade; builds frios neutros (70→68s, 85→86s). Ganhos O(N/k) exigiriam divisão em crates (workspace) — analisado teoricamente em r-35 §2.4." + }, + "testes": { + "cargo_total": 123, + "herdados_v090": 107, + "novos_v010": 16, + "novos": [ + "cp0a_sha256_vetores_fips", + "cp1a_salvar_carregar_roundtrip", + "cp1b_falha_preserva_anterior", + "cp3a_adulteracao_detectada", + "cp3b_truncamento_detectado", + "cp4a_manifesto_ausente_recusado", + "cp5a_manifesto_de_outro_recusado", + "tv3a_construtores_recusam_fora_do_dominio", + "tv3b_fechamento_inversao", + "tv3c_de_gamma_concorda", + "tv3d_nll_tipada_equivalente", + "tv3e_verificador_fronteira", + "ck1a_composicao_heterogenea_preserva_bt", + "ck1b_parametros_nao_vazios", + "ck1c_adaptador_bit_a_bit", + "ck2a_despacho_homeostatico" + ], + "saida": "ok" + }, + "checkpoints": { + "tokenizador_conceitos.json": { + "existe": true, + "valido_sha256": true, + "bytes": 240853123, + "manifesto_bytes": 141, + "hash_prefixo": "202e9d9fbeba8551…" + }, + "gerador_conceitos.json": { + "existe": true, + "valido_sha256": true, + "bytes": 59527584, + "manifesto_bytes": 140, + "hash_prefixo": "74a7a4595b1b4b20…" + } + }, + "treino_real": { + "a": { + "perda_inicial": 9.88181209564209, + "perda_final": 8.72581958770752, + "tempo_s": 449.4 + }, + "b": { + "perda_inicial": 4.1010067122323175, + "perda_final": 3.8218880721500943, + "tempo_s": 265.6 + }, + "c": { + "ce_inicial": 3.773000853402274, + "ce_final": 3.750825984137399, + "taxa_gatilho_media": null, + "ece_final": null + } + }, + "modulos": [ + { + "caminho": "src/fnh.rs", + "kB": 57.6 + }, + { + "caminho": "src/treino.rs", + "kB": 47.0 + }, + { + "caminho": "src/som4d.rs", + "kB": 37.2 + }, + { + "caminho": "src/verificador.rs", + "kB": 28.2 + }, + { + "caminho": "src/arvore_pesos.rs", + "kB": 27.8 + }, + { + "caminho": "src/controlador.rs", + "kB": 25.3 + }, + { + "caminho": "src/pensamento/motor.rs", + "kB": 24.8 + }, + { + "caminho": "src/tokenizador.rs", + "kB": 20.9 + }, + { + "caminho": "src/python/pensamento_demos.rs", + "kB": 19.3 + }, + { + "caminho": "src/moe_redirecionavel/nucleo.rs", + "kB": 18.8 + }, + { + "caminho": "src/telemetria.rs", + "kB": 18.4 + }, + { + "caminho": "src/nlg.rs", + "kB": 18.3 + } + ] +} \ No newline at end of file diff --git a/resultados/treino_v090.json b/resultados/treino_v090.json index e9b05b848a111ae97eada1f81cf7287518fd7f87..87b2a17c3cbe6d5230b86a403d612d4fd7948ef9 100644 --- a/resultados/treino_v090.json +++ b/resultados/treino_v090.json @@ -1,33 +1,33 @@ { "versao": "0.9.0", - "build": "khtst-rust-0.9.0", + "build": "khtst-rust-0.10.0", "dados": { "sequencias": 1121, "fonte": "wikimedia/wikipedia 20231101.pt" }, "fases": { "c": { - "acordos_medios": 0.5188963732356818, - "ce_final": 3.9293200288500105, - "ce_gatilhada_com_correcao": 4.091411646335356, - "ce_gatilhada_sem_correcao": 4.073794892270078, - "ce_inicial": 3.9499684742518832, - "confianca_media_final": 0.0002814010949805379, - "confianca_media_inicial": 0.00025644380366429687, - "confianca_pos_correcao": 0.0002146976511745203, - "decaimento_delta": 0.8004814077785324, - "ece_antes": 0.0153643210651353, - "ece_depois": 0.002648286405019462, - "escore_aprendizado": 0.20041952236591176, - "maior_sobreposicao_us": 5, - "passos": 169, - "rodadas_acoplador": 155, - "tau_c": 0.015690743923187256, + "acordos_medios": 0.5037088430928356, + "ce_final": 3.750825984137399, + "ce_gatilhada_com_correcao": 4.067766536110994, + "ce_gatilhada_sem_correcao": 4.0559665174328225, + "ce_inicial": 3.773000853402274, + "confianca_media_final": 0.18228887021541595, + "confianca_media_inicial": 0.1729295253753662, + "confianca_pos_correcao": 0.17216802735818676, + "decaimento_delta": 0.8236299321747604, + "ece_antes": 0.11367493326542899, + "ece_depois": 0.026873455557506542, + "escore_aprendizado": 0.20192427652934322, + "maior_sobreposicao_us": 10, + "passos": 180, + "rodadas_acoplador": 163, + "tau_c": 0.0171622633934021, "taxa_conflito_pcgrad": 0.0, - "taxa_gatilho": 0.6020710059171598, - "temperatura_calibrada": 8.0, + "taxa_gatilho": 0.5041666666666667, + "temperatura_calibrada": 0.2975706511869346, "timeouts_acoplador": 0, - "tempo_s": 403.8611032962799 + "tempo_s": 255.78383922576904 } }, "arvore": { @@ -168,17 +168,17 @@ "punicao_normalizada_fase_c": 0.0, "abacao_sintetica": { "punicao_alta": { - "delta_ort": 2.064895173658083e-07, + "delta_ort": 1.9774252730388464e-07, "gate_final": 0.9413755384972873, - "perda_final": 0.02014596201479435, - "perda_inicial": 0.02049672044813633, - "theta_norma": 0.042591993338429654 + "perda_final": 0.018947146832942963, + "perda_inicial": 0.020190276205539703, + "theta_norma": 0.06030914626997405 }, "punicao_baixa": { "delta_ort": 0.0, "gate_final": 0.0, - "perda_final": 0.022267581894993782, - "perda_inicial": 0.02211258001625538, + "perda_final": 0.03298451378941536, + "perda_inicial": 0.03268986567854881, "theta_norma": 0.0 } }, diff --git a/scripts/75_cli.py b/scripts/75_cli.py new file mode 100644 index 0000000000000000000000000000000000000000..8c93a4cfcd3482d892336a5e1a6cc96962c67695 --- /dev/null +++ b/scripts/75_cli.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +"""KHTST-RUST — CLI MLOps unificada (v0.10.0, crítica Qwen3.8 lote 3 nº 2). + +**Por que FAÇADA e não substituição:** a proposta original ("substituir +por um CLI declarativo") violaria duas regras do projeto: (a) os scripts +numerados são a trilha de rastreabilidade das memórias r-XX (cada prova +cita "scripts 39-74"); (b) aprimoramentos não removem funcionalidade +eficaz. O CLI unifica o ENTRY POINT (um só comando para todo o pipeline) +delegando via subprocess aos scripts canônicos — zero duplicação de +lógica, zero remoção — e acrescenta o que não existia: validação de +integridade criptográfica dos checkpoints ANTES do resume (Teo CP-1..3, +r-35 §4) e gestão de estados locais. + +Uso: + python scripts/75_cli.py versao + python scripts/75_cli.py treinar --validar-checkpoints + python scripts/75_cli.py verificar-checkpoints + python scripts/75_cli.py estados [--apagar] + python scripts/75_cli.py coletar|suite|telemetria|publicar [--confirmar] +""" +from __future__ import annotations + +import json +import os +import subprocess +import sys +from pathlib import Path +from typing import Optional + +import typer + +app = typer.Typer(help="Pipeline KHTST-RUST v0.10.0 — fachada unificada (r-35 §6)") + +RAIZ = Path(__file__).resolve().parent.parent +RESULTADOS = RAIZ / "resultados" +CHECKPOINTS = [ + RESULTADOS / "tokenizador_conceitos.json", + RESULTADOS / "gerador_conceitos.json", +] + +# scripts canônicos por estágio (trilha r-XX preservada) +DELEGACAO = { + "coletar": "70_coleta_shards_v090.py", + "treinar": "71_treino_v090.py", + "suite": "77_suite_v010.py", + "telemetria": "76_telemetria_v010.py", + "publicar": "78_publicar_v010.py", +} + +# padrões de estados de treino (mesma lista da varredura pós-publicação) +PADROES_ESTADO = [ + "*.pt", "*.npz", "*.ckpt", "*.safetensors", "*.estado.json", + "**/*.pt", "**/*.npz", "**/*.ckpt", "**/*.safetensors", "**/*.estado.json", +] + + +def _delega(estagio: str, extra: list[str] | None = None) -> int: + script = DELEGACAO[estagio] + cmd = [sys.executable, str(RAIZ / "scripts" / script)] + (extra or []) + typer.echo(f"→ {estagio}: {' '.join(cmd)}", err=True) + return subprocess.run(cmd, cwd=RAIZ).returncode + + +@app.command() +def versao() -> None: + """Versão do núcleo Rust e informação de build.""" + import khtst_rust as k + typer.echo(k.versao()) + typer.echo(k.info_build()) + + +@app.command() +def coletar() -> None: + """Coleta shards reais (Wikipédia PT-BR, streaming controlado).""" + raise typer.Exit(_delega("coletar")) + + +def _valida_ou_regenera(ck: Path, k) -> str: + """Política de resume com prova de integridade (CP-1..CP-3): + - não existe → 'ausente' (treina) + - manifesto bate (SHA256) → 'válido' (reusa — rápido) + - manifesto divergente → 'corrompido' (APAGA e retreina: nunca + reusa estado em silêncio) + - sem manifesto (legado) → 'legado' (reusa; o carregador legado + falha alto em JSON truncado — detecção por parse) + """ + if not ck.exists(): + return "ausente" + if k.checkpoint_valido(str(ck)): + return "válido" + tem_manifesto = k.checkpoint_manifesto(str(ck)) is not None + if tem_manifesto: + ck.unlink(missing_ok=True) + Path(str(ck) + ".manifest.json").unlink(missing_ok=True) + return "corrompido (apagado — será retreinado)" + return "legado (sem manifesto; parse do carregador detecta truncamento)" + + +@app.command() +def treinar( + validar_checkpoints: bool = typer.Option( + True, "--validar-checkpoints/--nao-validar", + help="Valida SHA-256 dos checkpoints antes do resume (Teo CP-3)."), +) -> None: + """Pipeline A/B/C (script 71) com pré-validação de integridade.""" + import khtst_rust as k + if validar_checkpoints: + for ck in CHECKPOINTS: + estado = _valida_ou_regenera(ck, k) + typer.echo(f"checkpoint {ck.name}: {estado}") + raise typer.Exit(_delega("treinar")) + + +@app.command() +def verificar_checkpoints() -> None: + """Relatório de integridade (SHA-256 + tamanho) dos checkpoints.""" + import khtst_rust as k + achou = False + for ck in RESULTADOS.rglob("*.json"): + if ck.name.endswith(".manifest.json"): + continue + manifesto = k.checkpoint_manifesto(str(ck)) + if manifesto is None: + continue + achou = True + m = json.loads(manifesto) + valido = k.checkpoint_valido(str(ck)) + typer.echo( + f"{'✓' if valido else '✗'} {ck.name}: sha256={m['hash_sha256'][:16]}… " + f"tamanho={m['tamanho']}B algoritmo={m['algoritmo']} " + f"versão={m['versao']}") + if not achou: + typer.echo("nenhum checkpoint com manifesto encontrado") + + +@app.command() +def suite() -> None: + """Suíte consolidada de gates v0.10.0 (script 77).""" + raise typer.Exit(_delega("suite")) + + +@app.command() +def telemetria() -> None: + """Coleta telemetria do projeto v0.10.0 (script 76).""" + raise typer.Exit(_delega("telemetria")) + + +@app.command() +def publicar( + confirmar: bool = typer.Option(False, "--confirmar", + help="Executa o upload único ao HF (script 74)."), +) -> None: + """Publicação organizada no HF — exige --confirmar.""" + if not confirmar: + typer.echo("publicação não confirmada: use --confirmar (upload único, " + "HF_TOKEN apenas via variável de ambiente)") + raise typer.Exit(2) + if not os.environ.get("HF_TOKEN"): + typer.echo("HF_TOKEN ausente no ambiente — defina antes de publicar") + raise typer.Exit(2) + raise typer.Exit(_delega("publicar")) + + +@app.command() +def estados( + apagar: bool = typer.Option(False, "--apagar", + help="Apaga os estados encontrados (regeneráveis)."), +) -> None: + """Varre (e opcionalmente apaga) estados de treino do ambiente local.""" + achados: list[Path] = [] + for padrao in PADROES_ESTADO: + achados.extend(RAIZ.rglob(padrao)) + achados = sorted(set(p for p in achados if "target" not in p.parts + and ".git" not in p.parts)) + if not achados: + typer.echo("0 estados de treino no ambiente local") + return + total = 0 + for p in achados: + tam = p.stat().st_size + total += tam + typer.echo(f"{'apagando' if apagar else 'achado'}: {p.relative_to(RAIZ)} " + f"({tam / 1e6:.1f} MB)") + if apagar: + p.unlink() + typer.echo(f"total: {len(achados)} arquivos, {total / 1e6:.1f} MB" + + (" — apagados" if apagar else " (use --apagar para remover)")) + + +if __name__ == "__main__": + app() diff --git a/scripts/76_telemetria_v010.py b/scripts/76_telemetria_v010.py new file mode 100644 index 0000000000000000000000000000000000000000..e858cbab06ac4105f1834319cb0db0ec1f16dcb8 --- /dev/null +++ b/scripts/76_telemetria_v010.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +"""76_telemetria_v010.py — telemetria do projeto v0.10.0 (críticas Qwen3.8 +lote 3): estrutura pós-divisão SRP, tempos de compilação (frios em target +dir separado + incrementais por toque — experimento da alegação de +"Amdahl da compilação"), contagens de testes, integridade dos checkpoints +(SHA-256) e inventário de módulos. +Saída: resultados/telemetria_v010.json +""" +import json, os, re, subprocess, time + +PASTA = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +def run(cmd, env=None, **kw): + return subprocess.run(cmd, capture_output=True, text=True, + cwd=PASTA, env=env, **kw) + +def main(): + rel = {"versao": "0.10.0"} + + # ---- estrutura: monólitos dissolvidos (SRP) + src = os.path.join(PASTA, "src") + arquivos = [] + for raiz, _, nfs in os.walk(src): + for n in nfs: + if n.endswith(".rs"): + p = os.path.join(raiz, n) + arquivos.append((os.path.relpath(p, PASTA), os.path.getsize(p))) + arquivos.sort(key=lambda x: -x[1]) + monolitos_v090 = { # tamanhos na v0.9.0 (pré-split) — r-34/medição + "src/python.rs": 112410, "src/jepa.rs": 58407, + "src/pensamento.rs": 53408, "src/moe_redirecionavel.rs": 50941, + } + rel["estrutura"] = { + "arquivos_fonte": len(arquivos), + "maior_arquivo": {"caminho": arquivos[0][0], "bytes": arquivos[0][1]}, + "monolitos_v090_bytes": monolitos_v090, + "subdirs": sorted({os.path.dirname(a) for a, _ in arquivos + if os.path.dirname(a) != "src"}), + "arquivos_por_diretorio": { + d: sum(1 for a, _ in arquivos if os.path.dirname(a) == d) + for d in sorted({os.path.dirname(a) for a, _ in arquivos})}, + } + + # ---- compilação (medições da sessão, target dirs separados p/ frios) + rel["compilacao"] = { + "frio_dominio_s": 68, # cargo test --no-default-features --no-run (CARGO_TARGET_DIR limpo) + "frio_completo_s": 86, # cargo test --all-features --no-run (CARGO_TARGET_DIR limpo) + "frio_dominio_v090_s": 70, "frio_completo_v090_s": 85, # r-34 (mesmo protocolo) + "overhead_python_pct": round(100 * (86 - 68) / 86, 1), + # experimento "Amdahl da compilação" (r-35 §2.3): toque único, + # mediana de 3, build incremental — monólito vs submódulo + "incremental_toque_s": { + "v090_jepa_rs_monolito": 1.06, "v010_jepa_variantes": 1.13, + "v090_pensamento_rs_monolito": 1.13, "v010_pensamento_motor": 1.15, + "v090_moe_rs_monolito": 1.05, "v010_moe_nucleo": 1.20, + "controle_tipos_rs_v090": 1.21, "controle_tipos_rs_v010": 1.19, + }, + "nota": "a alegação O(N/k) da crítica 1 é REFUTADA em granularidade " + "de arquivo: o custo incremental (~1,1s) é dominado pelo " + "overhead fixo do rustc/cargo, não pelo tamanho do arquivo " + "tocado (controle tipos.rs varia igual). O ganho comprovado " + "da divisão é SRP/manutenibilidade; builds frios neutros " + "(70→68s, 85→86s). Ganhos O(N/k) exigiriam divisão em crates " + "(workspace) — analisado teoricamente em r-35 §2.4.", + } + + # ---- testes (execução fresca; contagem oficial) + r = run(["cargo", "test", "--all-features", "--quiet"]) + m = re.findall(r"test result: ok\. (\d+) passed", r.stdout) + total = sum(int(x) for x in m) + rel["testes"] = { + "cargo_total": total, + "herdados_v090": 107, + "novos_v010": total - 107, + "novos": ["cp0a_sha256_vetores_fips", "cp1a_salvar_carregar_roundtrip", + "cp1b_falha_preserva_anterior", "cp3a_adulteracao_detectada", + "cp3b_truncamento_detectado", "cp4a_manifesto_ausente_recusado", + "cp5a_manifesto_de_outro_recusado", + "tv3a_construtores_recusam_fora_do_dominio", + "tv3b_fechamento_inversao", "tv3c_de_gamma_concorda", + "tv3d_nll_tipada_equivalente", "tv3e_verificador_fronteira", + "ck1a_composicao_heterogenea_preserva_bt", + "ck1b_parametros_nao_vazios", "ck1c_adaptador_bit_a_bit", + "ck2a_despacho_homeostatico"], + "saida": r.stdout[-400:] if r.returncode else "ok", + } + + # ---- integridade dos checkpoints (SHA-256 + manifesto) + import khtst_rust as k + R = os.path.join(PASTA, "resultados") + cks = {} + for nome in ("tokenizador_conceitos.json", "gerador_conceitos.json"): + p = os.path.join(R, nome) + if os.path.exists(p): + m_ = k.checkpoint_manifesto(p) + cks[nome] = { + "existe": True, "valido_sha256": k.checkpoint_valido(p), + "bytes": os.path.getsize(p), + "manifesto_bytes": os.path.getsize(p + ".manifest.json") if m_ else None, + "hash_prefixo": (json.loads(m_)["hash_sha256"][:16] + "…") if m_ else None, + } + else: + cks[nome] = {"existe": False} + rel["checkpoints"] = cks + + # ---- treino real v0.10.0 (Wikipédia PT-BR — pipeline A/B/C fresco) + treino = {} + for f, chave in (("fase_a_v010.json", "a"), ("fase_b_v010.json", "b")): + p = os.path.join(R, f) + if os.path.exists(p): + d = json.load(open(p)) + treino[chave] = {"perda_inicial": d["resumo"]["perda_inicial"], + "perda_final": d["resumo"]["perda_final"], + "tempo_s": round(d["tempo_s"], 1)} + p = os.path.join(R, "treino_v090.json") + if os.path.exists(p): + d = json.load(open(p)) + c = d.get("fases", {}).get("c", {}) + if c: + treino["c"] = {k2: c.get(k2) for k2 in + ("ce_inicial", "ce_final", "taxa_gatilho_media", "ece_final")} + rel["treino_real"] = treino + + # ---- inventário de módulos (top 12 por tamanho) + rel["modulos"] = [{"caminho": a, "kB": round(b / 1e3, 1)} for a, b in arquivos[:12]] + + saida = os.path.join(R, "telemetria_v010.json") + json.dump(rel, open(saida, "w"), indent=1, ensure_ascii=False) + print(f"OK: {saida}") + print(json.dumps({k: rel[k] for k in ("estrutura", "compilacao", "testes")}, + indent=1, ensure_ascii=False)[:1200]) + +if __name__ == "__main__": + main() diff --git a/scripts/77_suite_v010.py b/scripts/77_suite_v010.py new file mode 100644 index 0000000000000000000000000000000000000000..97627d2981996efdf16d6d452c46f13f4f6e4986 --- /dev/null +++ b/scripts/77_suite_v010.py @@ -0,0 +1,141 @@ +#!/usr/bin/env python3 +"""77_suite_v010.py — v0.10.0: VEREDITO CONSOLIDADO das críticas Qwen3.8 +(lote 3 — análise/prova/teste em r-35). + +Herda os gates da v0.9.0 (suite_v090_final.json) e adiciona: + s1 divisão SRP: nenhum arquivo-fonte > 60 kB; subdiretórios presentes; + testes herdados verdes (preservação de comportamento); + s2 checkpoint atômico: manifestos SHA-256 presentes e válidos nos + checkpoints do treino REAL; overhead de armazenamento ≈ 141 B; + s3 tipos de domínio: cargo inclui os testes T-V3 (contados em 76); + s4 polimorfismo de camadas: testes CK no cargo (idem); + s5 treino real: fases A/B com perda decrescente; C com CE decrescente; + δ_ort < 1e-5 (ortogonalidade exata em treino real); + s6 CLI: comandos versao/estados/verificar-checkpoints executam. +Saída: resultados/suite_v010_final.json +""" +import json, os, subprocess, sys + +PASTA = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +R = os.path.join(PASTA, "resultados") + +def main(): + rel = {"versao": "0.10.0", "blocos": {}, "gates": {}, "medicoes": {}} + + def carrega(arq): + p = os.path.join(R, arq) + return json.load(open(p)) if os.path.exists(p) else None + + # ---- legado v0.9.0 + legado = carrega("suite_v090_final.json") + if legado: + g_legado = legado.get("gates", legado) + ok = sum(1 for v in g_legado.values() if v is True) + rel["blocos"]["legado_v090"] = f"{ok}/{len(g_legado)}" + for k, v in g_legado.items(): + rel["gates"][f"legado_{k}"] = v + + # ---- s1: divisão SRP + arquivos = [] + for raiz, _, nfs in os.walk(os.path.join(PASTA, "src")): + for n in nfs: + if n.endswith(".rs"): + p = os.path.join(raiz, n) + arquivos.append((os.path.relpath(p, PASTA), os.path.getsize(p))) + maior = max(arquivos, key=lambda x: x[1]) + rel["medicoes"]["maior_arquivo"] = {"caminho": maior[0], "kB": round(maior[1] / 1e3, 1)} + rel["gates"]["s1_nenhum_arquivo_acima_60kB"] = maior[1] < 60_000 + subs = {os.path.dirname(a) for a, _ in arquivos} - {"src"} + rel["gates"]["s1_monolitos_dissolvidos"] = all( + d in subs for d in ("src/jepa", "src/pensamento", + "src/moe_redirecionavel", "src/python")) + # testes herdados: 107 da v0.9.0 continuam verdes (123/123 total) + r = subprocess.run(["cargo", "test", "--all-features", "--quiet"], + capture_output=True, text=True, cwd=PASTA) + import re as _re + m = _re.findall(r"test result: ok\. (\d+) passed", r.stdout) + total = sum(int(x) for x in m) + rel["medicoes"]["cargo_total"] = total + rel["gates"]["s1_testes_herdados_preservados"] = (total >= 123 and r.returncode == 0) + rel["blocos"]["s1_divisao_srp"] = "3/3" if all( + rel["gates"][g] for g in list(rel["gates"]) if g.startswith("s1_")) else "?" + + # ---- s2: checkpoints atômicos no treino real + import khtst_rust as k + cks_ok, cks = 0, {} + for nome in ("tokenizador_conceitos.json", "gerador_conceitos.json"): + p = os.path.join(R, nome) + okc = os.path.exists(p) and k.checkpoint_valido(p) + cks_ok += int(okc) + man = k.checkpoint_manifesto(p) if os.path.exists(p) else None + cks[nome] = {"valido": okc, + "manifesto_bytes": len(man) if man else 0} + rel["gates"]["s2_checkpoints_sha256_validos"] = (cks_ok == 2) + rel["medicoes"]["checkpoints"] = cks + rel["blocos"]["s2_checkpoint_atomico"] = f"{cks_ok}/2" + + # ---- s3+s4: testes novos T-V3 e CK contabilizados no cargo (via 76) + tel = carrega("telemetria_v010.json") + if tel: + n_novos = tel["testes"]["cargo_total"] - tel["testes"]["herdados_v090"] + rel["gates"]["s3s4_testes_novos_16"] = (n_novos >= 16) + rel["medicoes"]["testes_novos_v010"] = n_novos + rel["blocos"]["s3s4_tipos_trait"] = "1/1" if rel["gates"].get("s3s4_testes_novos_16") else "0/1" + + # ---- s5: treino real v0.10.0 + treino = {} + for f, chave in (("fase_a_v010.json", "a"), ("fase_b_v010.json", "b")): + d = carrega(f) + if d: + treino[chave] = d["resumo"] + d = carrega("treino_v090.json") + c = (d or {}).get("fases", {}).get("c", {}) + g5 = [] + g5.append(("s5a_faseA_perda_cai", treino.get("a", {}).get("perda_final", 1e9) + < treino.get("a", {}).get("perda_inicial", -1e9))) + g5.append(("s5b_faseB_perda_cai", treino.get("b", {}).get("perda_final", 1e9) + < treino.get("b", {}).get("perda_inicial", -1e9))) + g5.append(("s5c_faseC_ce_cai", c.get("ce_final", 1e9) < c.get("ce_inicial", -1e9))) + delta_ort = treino.get("a", {}).get("delta_ort_max", 1.0) + g5.append(("s5d_ortogonalidade_exata", delta_ort < 1e-5)) + for nome, val in g5: + rel["gates"][nome] = val + rel["medicoes"]["treino_real"] = { + "a": {"perda_inicial": treino.get("a", {}).get("perda_inicial"), + "perda_final": treino.get("a", {}).get("perda_final"), + "delta_ort_max": delta_ort}, + "b": {"perda_inicial": treino.get("b", {}).get("perda_inicial"), + "perda_final": treino.get("b", {}).get("perda_final")}, + "c": {"ce_inicial": c.get("ce_inicial"), "ce_final": c.get("ce_final"), + "taxa_gatilho": c.get("taxa_gatilho_media")}, + } + rel["blocos"]["s5_treino_real"] = f"{sum(1 for _, v in g5 if v)}/{len(g5)}" + + # ---- s6: CLI + cli = os.path.join(PASTA, "scripts", "75_cli.py") + r1 = subprocess.run([sys.executable, cli, "versao"], capture_output=True, text=True) + r2 = subprocess.run([sys.executable, cli, "estados"], capture_output=True, text=True) + r3 = subprocess.run([sys.executable, cli, "verificar-checkpoints"], + capture_output=True, text=True) + rel["gates"]["s6_cli_comandos"] = all( + r.returncode == 0 for r in (r1, r2, r3)) and "0.10.0" in r1.stdout + rel["medicoes"]["cli"] = {"versao": r1.stdout.strip().splitlines()[0], + "checkpoints_relacionados": len(r3.stdout.strip().splitlines())} + rel["blocos"]["s6_cli"] = "1/1" if rel["gates"]["s6_cli_comandos"] else "0/1" + + # ---- veredito + gates = rel["gates"] + ok = sum(1 for v in gates.values() if v is True) + rel["veredito"] = f"{ok}/{len(gates)} — " + ( + "APROVADO" if ok == len(gates) else "REPROVADO") + saida = os.path.join(R, "suite_v010_final.json") + json.dump(rel, open(saida, "w"), indent=1, ensure_ascii=False) + print(f"OK: {saida}") + print("blocos:", json.dumps(rel["blocos"], ensure_ascii=False)) + print("VEREDITO:", rel["veredito"]) + falhas = [k for k, v in gates.items() if v is not True] + if falhas: + print("falhas:", falhas) + +if __name__ == "__main__": + main() diff --git a/scripts/78_publicar_v010.py b/scripts/78_publicar_v010.py new file mode 100644 index 0000000000000000000000000000000000000000..a4c1dcc6ac24ee1ff515cc89bcb01dfa47a75f76 --- /dev/null +++ b/scripts/78_publicar_v010.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python3 +"""78_publicar_v010.py — Publicação da v0.10.0 no HF (UM ÚNICO upload_folder +organizado — o HF bloqueia envios parciais em sequência; upload atômico). + +Fases: + 1. inventário da conta + APAGAMENTO de estados de treino remanescentes; + 2. varredura local do VALOR do token (nenhum arquivo pode contê-lo); + 3. UM ÚNICO upload_folder com ignore_patterns EXPLÍCITOS + + delete_patterns p/ monólitos dissolvidos (src/jepa.rs → src/jepa/…); + 4. verificação pós-upload (0 estados, módulos-chave presentes); + 5. varredura REMOTA do valor do token; + 6. apagamento dos estados de treino LOCAIS (checkpoints + manifestos). + +v0.10.0: manifestos (*.manifest.json) NÃO são publicados — só têm sentido +ao lado do checkpoint correspondente (que também não é publicado). +Token: EXCLUSIVAMENTE via variável de ambiente HF_TOKEN (nunca em arquivo). +""" +import os, sys, tempfile + +ARQUIVOS_EXCLUIR_DIRS = {"target", ".cargo", "__pycache__", ".git", ".venv", + "skills", "externos"} +ARQUIVOS_EXCLUIR_NOME = {"shard_treino.jsonl", "shard_validacao.jsonl", + "bpe.json", "rustup-init.sh"} +SUFIXOS_ESTADO = (".pt", ".pth", ".ckpt", ".safetensors", ".bin", ".npz", + ".msgpack") +NOMES_ESTADO = {"gerador_conceitos.json", "tokenizador_conceitos.json", + "gerador_aritmetica.json", "_drill_ck.json"} +# monólitos da v0.9.0 dissolvidos em diretórios na v0.10.0 (r-35 §2) +MONOLITOS_VELHOS = ["src/python.rs", "src/jepa.rs", "src/pensamento.rs", + "src/moe_redirecionavel.rs"] + +def coletar(pasta): + arquivos = {} + for raiz, dirs, fs in os.walk(pasta): + dirs[:] = [d for d in dirs if d not in ARQUIVOS_EXCLUIR_DIRS] + for f in fs: + if f in ARQUIVOS_EXCLUIR_NOME or f in NOMES_ESTADO: + continue + if f.endswith(SUFIXOS_ESTADO) or f.endswith(".estado.json"): + continue + if f.endswith(".manifest.json") or f.endswith(".manifest.json.tmp"): + continue + if f.endswith(".so") or f in (".gitignore", "Cargo.lock.ori"): + continue + if f.startswith("hf_") or f.startswith("intros_"): + continue + caminho = os.path.join(raiz, f) + arquivos[os.path.relpath(caminho, pasta)] = caminho + return arquivos + +def varre_token_em_arquivos(token, pares): + tb = token.encode() + achados = [] + for rel, p in pares.items(): + try: + with open(p, "rb") as f: + if tb in f.read(): + achados.append(rel) + except OSError: + pass + return achados + +def main(): + from huggingface_hub import HfApi + token = os.environ.get("HF_TOKEN") + if not token: + print("ERRO: HF_TOKEN ausente"); sys.exit(1) + api = HfApi(token=token) + repo = "PowerMachine/khtst-rust" + + # ---------------- 1) inventário da conta + apaga estados ---------------- + print("=== 1) inventário da conta HF (procura estados de treino) ===") + repos = list(api.list_models(author="PowerMachine")) + print(f"repos: {len(repos)}") + total_removidos = 0 + for r in repos: + try: + files = api.list_repo_files(repo_id=r.id, repo_type="model") + except Exception as e: + print(f" {r.id}: erro ao listar ({e})"); continue + estados = [f for f in files + if f.endswith(SUFIXOS_ESTADO) + or f.split("/")[-1] in NOMES_ESTADO + or (".meta.json" in f and "checkpoint" in f)] + if estados: + print(f" {r.id}: APAGANDO {len(estados)} estados") + for f in estados: + api.delete_file(path_in_repo=f, repo_id=r.id, repo_type="model") + total_removidos += len(estados) + else: + print(f" {r.id}: {len(files)} arquivos, 0 estados") + print(f"estados removidos no total: {total_removidos}") + + # ---------------- 2) varredura local do token ---------------- + pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + arquivos = coletar(pasta) + print(f"\n=== 2) varredura local: {len(arquivos)} arquivos ===") + achados = varre_token_em_arquivos(token, arquivos) + if achados: + print(f"ERRO: valor do token encontrado em: {achados}"); sys.exit(1) + print("varredura de token local: limpa") + + # ---------------- 3) UM ÚNICO upload_folder ---------------- + ignores = ( + [f"{d}/**" for d in ARQUIVOS_EXCLUIR_DIRS] + + [f"{d}" for d in ARQUIVOS_EXCLUIR_DIRS] + + [f"**/{n}" for n in ARQUIVOS_EXCLUIR_NOME] + + [f"**/{n}" for n in NOMES_ESTADO] + + list(SUFIXOS_ESTADO) + + ["*.so", ".gitignore", "hf_*.py", "hf_*.json", "intros_*.json", + "*.estado.json", "**/*.manifest.json", "**/*.manifest.json.tmp", + "*.tmp"] + ) + print(f"\n=== 3) upload_folder ÚNICO ({len(arquivos)} arquivos) ===") + api.upload_folder( + repo_id=repo, repo_type="model", folder_path=pasta, + ignore_patterns=ignores, + delete_patterns=MONOLITOS_VELHOS, + commit_message="v0.10.0: análise/prova/teste das críticas de " + "engenharia Qwen3.8 lote 3 (r-35) — divisão SRP dos monólitos " + "jepa/pensamento/moe/python em 26 submódulos (API estável via " + "re-exports; 'Amdahl de compilação' refutada por experimento " + "controlado, builds frios neutros), checkpoints atômicos SHA-256 " + "puro FIPS 180-4 + fsync + rename + manifesto (CP-1..CP-3; " + "correções da proposta: sem destruir estado anterior, manifesto " + "com barreira, sem bincode/unwrap), TemperaturaInversa T-V3 " + "(fronteira tipada da convexidade N-2; alegação compile-time " + "corrigida), trait CamadaKhtst+CamadaHomeostatica (premissa do " + "controlador refutada no fonte; Vec; adaptador MoeComPapel " + "bit-a-bit), CLI Typer 75 (fachada aditiva com resume verificado " + "por hash) — 123/123 cargo, 107/107 gates, treino real Wikipédia " + "PT-BR com checkpoints íntegros", + ) + print("upload concluído") + + # ---------------- 4) verificação pós-upload ---------------- + print("\n=== 4) verificação pós-upload ===") + files = api.list_repo_files(repo_id=repo, repo_type="model") + estados = [f for f in files if f.endswith(SUFIXOS_ESTADO) + or f.split("/")[-1] in NOMES_ESTADO + or f.endswith(".manifest.json")] + assert not estados, f"estados publicados: {estados}" + chaves = ["src/checkpoint.rs", "src/tipos.rs", "src/camadas.rs", + "src/jepa/mod.rs", "src/pensamento/mod.rs", + "src/moe_redirecionavel/mod.rs", "src/python/mod.rs", + "src/fnh.rs", "src/verificador.rs", + "scripts/75_cli.py", "scripts/76_telemetria_v010.py", + "scripts/77_suite_v010.py", "scripts/78_publicar_v010.py", + "docs/memorias/r-35-analise-provas-qwen-v010.md", + "resultados/suite_v010_final.json", + "resultados/telemetria_v010.json", + "resultados/fase_a_v010.json", "resultados/fase_b_v010.json", + "resultados/treino_v090.json"] + faltando = [c for c in chaves if c not in files] + assert not faltando, f"faltando: {faltando}" + velhos = [m for m in MONOLITOS_VELHOS if m in files] + assert not velhos, f"monólitos velhos ainda no repo: {velhos}" + print(f"repo: {len(files)} arquivos | 0 estados | módulos-chave OK | " + f"monólitos velhos removidos") + + # ---------------- 5) varredura REMOTA do token ---------------- + print("\n=== 5) varredura REMOTA do valor do token ===") + with tempfile.TemporaryDirectory() as tmp: + achados_remotos = [] + for f in files: + if f.endswith((".py", ".rs", ".md", ".json", ".toml", ".sh", ".cfg", + ".txt", ".jsonl")): + try: + p = api.hf_hub_download(repo_id=repo, repo_type="model", + filename=f, local_dir=tmp) + with open(p, "rb") as fh: + if token.encode() in fh.read(): + achados_remotos.append(f) + except Exception: + pass + if achados_remotos: + print(f"ERRO: token remoto em: {achados_remotos}"); sys.exit(1) + print("varredura remota: limpa") + + # ---------------- 6) apaga estados de treino LOCAIS ---------------- + print("\n=== 6) estados de treino locais (checkpoints + manifestos) ===") + apagados = 0 + for nome in sorted(NOMES_ESTADO): + for sufixo in ("", ".manifest.json"): + p = os.path.join(pasta, "resultados", nome + sufixo) + if os.path.exists(p): + os.remove(p) + apagados += 1 + print(f" apagado: resultados/{nome}{sufixo}") + print(f"locais apagados: {apagados}") + print("\nPUBLICAÇÃO v0.10.0 CONCLUÍDA") + +if __name__ == "__main__": + main() diff --git a/src/camadas.rs b/src/camadas.rs index e67116ab232974826c327a9ea196aa5c8712d321..0339238ff15a7bd2b6d47564027abcde36dc3dd4 100644 --- a/src/camadas.rs +++ b/src/camadas.rs @@ -136,3 +136,165 @@ impl Swiglu { [self.w1.vars(), self.w3.vars(), self.w2.vars()].concat() } } + +// =========================================================================== +// v0.10.0 — Polimorfismo de camadas (crítica Qwen3.8 lote 3, proposta 1, +// CORRIGIDA; r-35 §3). A proposta original tinha três problemas: +// +// (a) a PREMISSA era falsa: `controlador.rs` NÃO referencia Fnh/Moe/Jepa +// (verificado por busca no fonte — o controlador regula +// hiperparâmetros, não despacha camadas); +// (b) `parametros() -> Vec<&Tensor>` QUEBRA o otimizador: a partir de +// `&Tensor` não se recupera a `Var` para atualização in-place — a +// assinatura correta devolve `Vec`; +// (c) `gate_ativacao` na interface-base viola a segregação de +// interface: nem toda camada tem gate de punição. Movida para o +// trait `CamadaHomeostatica` (implantável só por quem tem gate). +// +// A "prova" da proposta ("composição de funções lineares e ativações +// preserva a dimensionalidade do batch") também está mal tipada: D_in → +// D_out PODE mudar (é o que uma projeção faz). O invariante correto: +// +// **Teo CK-1 (invariante de composição).** Se f: R^{B×T×D1} → R^{B×T×D2} +// e g: R^{B×T×D2} → R^{B×T×D3} operam apenas (i) mapas lineares na +// ÚLTIMA dimensão (via reshape (B·T, D) → (B·T, D') → reshape) e (ii) +// ativações elemento-a-elemento, então g∘f: R^{B×T×D1} → R^{B×T×D3} — +// as dimensões de lote B e tempo T são preservadas por toda a composição. +// Prova: o reshape é bijeção canônica R^{B×T×D} ≅ R^{(B·T)·D} que fixa +// o produto B·T; matmul por W ∈ R^{D'×D} age à direita (índice de +// coluna) e não permuta as linhas; ativações elemento-a-elemento agem +// coordenada-a-coordenada. Logo cada estágio devolve exatamente B·T +// linhas, e o reshape inverso restaura (B, T). ∎ +// =========================================================================== + +/// Camada com forward canônico R^{B×T×D_in} → R^{B×T×D_out} (Teo CK-1). +pub trait CamadaKhtst { + /// Forward preservando as dimensões de lote/tempo (B, T). + fn forward(&self, x: &Tensor) -> Result; + /// Parâmetros treináveis como `Var` — o otimizador atualiza in-place + /// (correção (b): `Vec<&Tensor>` romperia o SGD). + fn parametros(&self) -> Vec; +} + +/// Camada com gate de punição homeostático (segregação, correção (c)). +/// g = max(0, 2σ((π − θ_π)/τ_g) − 1) — zona morta exata (Teo F-3). +pub trait CamadaHomeostatica { + fn gate_ativacao(&self, punicao: &crate::tipos::Probabilidade) + -> crate::tipos::GateAtivacao; +} + +impl CamadaKhtst for Linear { + fn forward(&self, x: &Tensor) -> Result { + self.fwd(x) + } + fn parametros(&self) -> Vec { + self.vars() + } +} + +impl CamadaKhtst for crate::jepa::MlpJepa { + fn forward(&self, x: &Tensor) -> Result { + self.fwd(x) + } + fn parametros(&self) -> Vec { + [self.l1.vars(), self.l2.vars()].concat() + } +} + +/// Adaptador zero-custo que CAPTURA o papel (codificador/decodificador) +/// da MoE redirecionável, expondo o forward canônico do trait SEM perder +/// o parâmetro: os caminhos de treino continuam usando `forward(x, papel)` +/// completo (com perda auxiliar e carga — nada é removido); o adaptador +/// serve a composição heterogênea de INFERÊNCIA. +pub struct MoeComPapel<'a> { + pub moe: &'a crate::moe_redirecionavel::MoeRedirecionavel, + pub papel: crate::moe_redirecionavel::Papel, +} + +impl CamadaKhtst for MoeComPapel<'_> { + fn forward(&self, x: &Tensor) -> Result { + Ok(self.moe.forward(x, self.papel)?.saida) + } + fn parametros(&self) -> Vec { + self.moe.vars() + } +} + +#[cfg(test)] +mod testes_camada_khtst { + use super::*; + + /// CK-1a — composição heterogênea `Vec<&dyn CamadaKhtst>` preserva + /// B×T (2, 3) através de Linear→MlpJepa (Teo CK-1) e o adaptador MoE. + #[test] + fn ck1a_composicao_heterogenea_preserva_bt() { + let dev = Device::Cpu; + let (b, t, d) = (2usize, 3usize, 8usize); + let lin = Linear::novo(d, 16, 0.02, &dev).unwrap(); + let mlp = crate::jepa::MlpJepa::novo(16, 24, 32, &dev).unwrap(); + let x0 = Tensor::randn(0f32, 1f32, (b, t, d), &dev).unwrap(); + + let camadas: Vec<&dyn CamadaKhtst> = vec![&lin, &mlp]; + let mut x = x0.clone(); + for c in &camadas { + x = c.forward(&x).unwrap(); + } + assert_eq!(x.dims(), &[b, t, 32], "B×T preservado (Teo CK-1)"); + + // adaptador MoE sobre o achatado (B·T, D) — forward canônico + let moe = crate::moe_redirecionavel::MoeRedirecionavel::novo(32, 4, 2, 7, &dev).unwrap(); + let adapt = MoeComPapel { + moe: &moe, + papel: crate::moe_redirecionavel::Papel::Codificador, + }; + let x2d = x.reshape((b * t, 32)).unwrap(); + let y = ::forward(&adapt, &x2d).unwrap(); + assert_eq!(y.dims(), &[b * t, 32]); + } + + /// CK-1b — `parametros()` devolve `Var` NÃO-VAZIAS (otimizável). + #[test] + fn ck1b_parametros_nao_vazios() { + let dev = Device::Cpu; + let lin = Linear::novo(8, 16, 0.02, &dev).unwrap(); + let mlp = crate::jepa::MlpJepa::novo(16, 24, 32, &dev).unwrap(); + let moe = crate::moe_redirecionavel::MoeRedirecionavel::novo(32, 4, 2, 7, &dev).unwrap(); + let adapt = MoeComPapel { moe: &moe, papel: crate::moe_redirecionavel::Papel::Codificador }; + for (nome, n) in [("linear", lin.parametros().len()), + ("mlp", mlp.parametros().len()), + ("moe", adapt.parametros().len())] { + assert!(n > 0, "{nome} deveria ter parâmetros"); + } + } + + /// CK-1c — o adaptador delega BIT-A-BIT para moe.forward(x, papel). + #[test] + fn ck1c_adaptador_bit_a_bit() { + let dev = Device::Cpu; + let moe = crate::moe_redirecionavel::MoeRedirecionavel::novo(16, 4, 2, 11, &dev).unwrap(); + let x = Tensor::randn(0f32, 1f32, (5, 16), &dev).unwrap(); + let direto = moe.forward(&x, crate::moe_redirecionavel::Papel::Codificador).unwrap(); + let adapt = MoeComPapel { moe: &moe, papel: crate::moe_redirecionavel::Papel::Codificador }; + let via_trait = ::forward(&adapt, &x).unwrap(); + let a = direto.saida.flatten_all().unwrap().to_vec1::().unwrap(); + let bvia = via_trait.flatten_all().unwrap().to_vec1::().unwrap(); + assert_eq!(a.len(), bvia.len()); + for (i, (x1, x2)) in a.iter().zip(bvia.iter()).enumerate() { + assert_eq!(x1.to_bits(), x2.to_bits(), "elem {i} difere"); + } + } + + /// CK-2a — despacho homeostático heterogêneo: zona morta exata em + /// π < θ_π e ativação > 0 em π alto (Teo F-3 via fronteira tipada). + #[test] + fn ck2a_despacho_homeostatico() { + let gate = crate::fnh::GatePunicao::default(); + let camadas: Vec<&dyn CamadaHomeostatica> = vec![&gate]; + let baixa = crate::tipos::Probabilidade::novo(0.2).unwrap(); + let alta = crate::tipos::Probabilidade::novo(0.95).unwrap(); + for c in &camadas { + assert_eq!(c.gate_ativacao(&baixa).valor(), 0.0, "zona morta exata"); + assert!(c.gate_ativacao(&alta).valor() > 0.0, "gate abre sob punição alta"); + } + } +} diff --git a/src/checkpoint.rs b/src/checkpoint.rs new file mode 100644 index 0000000000000000000000000000000000000000..20e4bc0437f0be38b83358ae42034d198eee73db --- /dev/null +++ b/src/checkpoint.rs @@ -0,0 +1,353 @@ +//! Checkpoints atômicos com prova de integridade criptográfica (v0.10.0). +//! +//! **Crítica Qwen3.8 lote 3, nº 3 ("Checkpointing Não-Atômico") — ACEITA +//! COM CORREÇÕES (r-35 §4).** A proposta original tinha três falhas: +//! +//! 1. **Destruição do estado anterior:** `File::create(caminho_pesos)` +//! trunca o checkpoint ANTERIOR antes de escrever o novo — uma queda +//! de energia no meio da escrita perde AMBOS. A atomicidade real exige +//! escrita em `.tmp` + `rename(2)` (o POSIX garante troca atômica). +//! 2. **Manifesto sem barreira:** `fs::write(caminho_meta, ...)` sem +//! `sync_all` — o manifesto pode não atingir o disco quando os pesos +//! já atingiram (ou vice-versa), violando a própria ordenação alegada. +//! 3. **Dependência nova (bincode) + `unwrap()`** em caminho de I/O. +//! +//! Esta implementação: SHA-256 **puro em Rust** (FIPS 180-4, sem crates +//! externas — crates.io inacessível no ambiente), escrita tmp→fsync→ +//! rename→fsync(dir) para AMBOS os arquivos, e manifesto lado-a-lado +//! `{caminho}.manifest.json` com hash + tamanho. +//! +//! **Teo CP-1 (atomicidade da troca).** `rename(2)` é atômico em relação +//! a leitores concorrentes e crashes: em qualquer instante o caminho +//! resolve OU para o arquivo antigo OU para o novo, nunca uma mistura. +//! Logo um crash em qualquer ponto deixa o caminho final = checkpoint +//! antigo (crash antes do rename) ou novo (depois). ∎ (garantia POSIX) +//! +//! **Teo CP-2 (durabilidade ordenada).** Com a disciplina +//! `write(W.tmp) → fsync(W.tmp) → rename(W) → write(M.tmp) → +//! fsync(M.tmp) → rename(M) → fsync(dir)`, após reboot: se `M` existe, +//! então o rename de `W` foi durável (executado e journalizado antes) e +//! os blocos de dados de `W` foram forçados por `fsync(W.tmp)` — portanto +//! `SHA-256(W) = M.hash` (a menos de colisão, CP-3). Se `M` não existe, +//! o estado é tratado como INVÁLIDO (não silenciosamente errado). ∎ +//! +//! **Teo CP-3 (detecção de corrupção).** Para W' ≠ W com |W'| = |W|, +//! `P[SHA-256(W') = SHA-256(W)] ≤ 2^-256` (ataque direto) e, para +//! qualquer par distinto, ≤ 2^-128 (limite de aniversário). Truncamentos +//! e gravações parciais são capturados DETERMINISTICAMENTE pelo campo +//! `tamanho`. Nota honesta: a alegação original "matematicamente +//! idêntico" é exagerada — o correto é indistinguibilidade computacional +//! com probabilidade de colisão ≤ 2^-128. ∎ +//! +//! **Compatibilidade (regra da casa: nada é removido):** checkpoints da +//! v0.9.0 (sem manifesto) continuam carregáveis pelos `carregar` legados; +//! `carregar_verificado` exige manifesto e recusa o contrário. + +use candle_core::Result; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +pub const VERSAO_CHECKPOINT: &str = "0.10.0"; + +// --------------------------------------------------------------------------- +// SHA-256 — FIPS 180-4, implementação autocontida (sem dependências) +// --------------------------------------------------------------------------- + +const K_SHA256: [u32; 64] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, + 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, + 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, + 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, + 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, + 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, + 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, + 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, + 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +]; + +/// SHA-256 de `dados` — 32 bytes (FIPS 180-4 §6.2.2). +pub fn sha256(dados: &[u8]) -> [u8; 32] { + let mut h: [u32; 8] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + ]; + // padding: 0x80, zeros, comprimento de bit big-endian em 64 bits + let bitlen = (dados.len() as u64).wrapping_mul(8); + let mut msg = dados.to_vec(); + msg.push(0x80); + while msg.len() % 64 != 56 { + msg.push(0); + } + msg.extend_from_slice(&bitlen.to_be_bytes()); + + for bloco in msg.chunks_exact(64) { + let mut w = [0u32; 64]; + for (i, j) in (0..64).step_by(4).enumerate() { + w[i] = u32::from_be_bytes([bloco[j], bloco[j + 1], bloco[j + 2], bloco[j + 3]]); + } + for t in 16..64 { + let s0 = w[t - 15].rotate_right(7) ^ w[t - 15].rotate_right(18) ^ (w[t - 15] >> 3); + let s1 = w[t - 2].rotate_right(17) ^ w[t - 2].rotate_right(19) ^ (w[t - 2] >> 10); + w[t] = w[t - 16] + .wrapping_add(s0) + .wrapping_add(w[t - 7]) + .wrapping_add(s1); + } + let (mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut hh) = + (h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]); + for t in 0..64 { + let s1 = e.rotate_right(6) ^ e.rotate_right(11) ^ e.rotate_right(25); + let ch = (e & f) ^ ((!e) & g); + let t1 = hh + .wrapping_add(s1) + .wrapping_add(ch) + .wrapping_add(K_SHA256[t]) + .wrapping_add(w[t]); + let s0 = a.rotate_right(2) ^ a.rotate_right(13) ^ a.rotate_right(22); + let maj = (a & b) ^ (a & c) ^ (b & c); + let t2 = s0.wrapping_add(maj); + hh = g; g = f; f = e; e = d.wrapping_add(t1); + d = c; c = b; b = a; a = t1.wrapping_add(t2); + } + h[0] = h[0].wrapping_add(a); h[1] = h[1].wrapping_add(b); + h[2] = h[2].wrapping_add(c); h[3] = h[3].wrapping_add(d); + h[4] = h[4].wrapping_add(e); h[5] = h[5].wrapping_add(f); + h[6] = h[6].wrapping_add(g); h[7] = h[7].wrapping_add(hh); + } + let mut saida = [0u8; 32]; + for (i, x) in h.iter().enumerate() { + saida[i * 4..i * 4 + 4].copy_from_slice(&x.to_be_bytes()); + } + saida +} + +/// SHA-256 em hexadecimal minúsculo (64 caracteres). +pub fn sha256_hex(dados: &[u8]) -> String { + sha256(dados).iter().map(|b| format!("{b:02x}")).collect() +} + +// --------------------------------------------------------------------------- +// salva / carrega atômicos +// --------------------------------------------------------------------------- + +/// Caminho do manifesto lado-a-lado de `caminho`. +pub fn caminho_manifesto(caminho: &str) -> String { + format!("{caminho}.manifest.json") +} + +fn err_io(e: std::io::Error, ctx: &str) -> candle_core::Error { + candle_core::Error::Msg(format!("{ctx}: {e}")) +} + +/// fsync de um diretório (durabilidade do rename — POSIX). Unix apenas; +/// em outras plataformas é no-op documentado. +fn fsync_dir(dir: &Path) -> std::io::Result<()> { + #[cfg(unix)] + { + let d = File::open(dir)?; + d.sync_all()?; + } + #[cfg(not(unix))] + { + let _ = dir; + } + Ok(()) +} + +/// Escrita atômica de `conteudo` em `caminho` com fsync + rename e +/// manifesto `{caminho}.manifest.json` (hash SHA-256 + tamanho). +/// +/// Ordem (Teo CP-2): write(tmp) → fsync(tmp) → rename(final) para o +/// conteúdo e para o manifesto; fsync(dir) ao final. +pub fn salvar_atomico(caminho: &str, conteudo: &str) -> Result<()> { + let dados = conteudo.as_bytes(); + let hash = sha256_hex(dados); + let tmp = format!("{caminho}.tmp"); + + // 1) conteúdo: tmp → fsync → rename (Teo CP-1: troca atômica) + { + let mut f = File::create(&tmp).map_err(|e| err_io(e, "checkpoint: criar tmp"))?; + f.write_all(dados).map_err(|e| err_io(e, "checkpoint: escrever tmp"))?; + f.sync_all().map_err(|e| err_io(e, "checkpoint: fsync tmp"))?; + } + std::fs::rename(&tmp, caminho).map_err(|e| err_io(e, "checkpoint: rename"))?; + + // 2) manifesto: mesma disciplina (a proposta original usava fs::write + // simples — sem barreira, o manifesto pode perder-se sozinho) + let manifest = serde_json::json!({ + "versao": VERSAO_CHECKPOINT, + "algoritmo": "sha256", + "hash_sha256": hash, + "tamanho": dados.len(), + }); + let mtxt = serde_json::to_string(&manifest) + .map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?; + let mtmp = format!("{}.manifest.json.tmp", caminho); + { + let mut f = File::create(&mtmp).map_err(|e| err_io(e, "manifesto: criar tmp"))?; + f.write_all(mtxt.as_bytes()).map_err(|e| err_io(e, "manifesto: escrever tmp"))?; + f.sync_all().map_err(|e| err_io(e, "manifesto: fsync tmp"))?; + } + std::fs::rename(&mtmp, caminho_manifesto(caminho)) + .map_err(|e| err_io(e, "manifesto: rename"))?; + + // 3) durabilidade dos renames (POSIX: rename journalizado no dir) + if let Some(pai) = Path::new(caminho).parent() { + fsync_dir(pai).map_err(|e| err_io(e, "checkpoint: fsync dir"))?; + } + Ok(()) +} + +/// Lê `caminho` verificando o manifesto (hash + tamanho). Recusa +/// manifesto ausente ou divergente — Teo CP-3. +pub fn carregar_verificado(caminho: &str) -> Result { + let dados = std::fs::read(caminho).map_err(|e| err_io(e, "checkpoint: ler"))?; + let mtxt = std::fs::read_to_string(caminho_manifesto(caminho)) + .map_err(|_| candle_core::Error::Msg(format!( + "checkpoint sem manifesto (legado v0.9.0?): {caminho}")))?; + let m: serde_json::Value = serde_json::from_str(&mtxt) + .map_err(|e| candle_core::Error::Msg(format!("manifesto ilegível: {e}")))?; + let hash_esp = m["hash_sha256"].as_str().unwrap_or(""); + let tam_esp = m["tamanho"].as_u64().unwrap_or(u64::MAX); + let hash_rec = sha256_hex(&dados); + if (dados.len() as u64) != tam_esp { + candle_core::bail!( + "checkpoint corrompido: tamanho {} ≠ manifesto {} ({caminho})", + dados.len(), tam_esp); + } + if hash_rec != hash_esp { + candle_core::bail!( + "checkpoint corrompido: SHA-256 divergente ({caminho})"); + } + String::from_utf8(dados) + .map_err(|e| candle_core::Error::Msg(format!("utf8: {e}"))) +} + +/// Verificação sem exceção (para uso em Python/CLI antes de resume). +pub fn validar(caminho: &str) -> bool { + carregar_verificado(caminho).is_ok() +} + +#[cfg(test)] +mod testes_checkpoint { + use super::*; + + fn tmp_dir(nome: &str) -> std::path::PathBuf { + let d = std::env::temp_dir().join(format!("khtst_cp_{nome}_{}", std::process::id())); + let _ = std::fs::remove_dir_all(&d); + std::fs::create_dir_all(&d).unwrap(); + d + } + + /// CP-0a — SHA-256 contra os vetores oficiais FIPS 180-4. + #[test] + fn cp0a_sha256_vetores_fips() { + assert_eq!( + sha256_hex(b""), + "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); + assert_eq!( + sha256_hex(b"abc"), + "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"); + assert_eq!( + sha256_hex(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), + "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1"); + // vetor de 4 blocos (256 bytes) — exercita o laço de blocos + let longo = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmn".repeat(4); + assert_eq!(sha256_hex(longo.as_bytes()).len(), 64); + } + + /// CP-1a — salvar produz final + manifesto; tmp não permanece; + /// carregar_verificado devolve o conteúdo bit-a-bit. + #[test] + fn cp1a_salvar_carregar_roundtrip() { + let d = tmp_dir("rt"); + let p = d.join("ck.json"); + salvar_atomico(p.to_str().unwrap(), "{\"v0\":[1.0,2.0]}").unwrap(); + assert!(p.exists()); + assert!(d.join("ck.json.manifest.json").exists()); + assert!(!d.join("ck.json.tmp").exists()); + assert!(!d.join("ck.json.manifest.json.tmp").exists()); + assert_eq!(carregar_verificado(p.to_str().unwrap()).unwrap(), + "{\"v0\":[1.0,2.0]}"); + let _ = std::fs::remove_dir_all(&d); + } + + /// CP-3a — adulteração de 1 byte é detectada (hash divergente). + #[test] + fn cp3a_adulteracao_detectada() { + let d = tmp_dir("t"); + let p = d.join("ck.json"); + salvar_atomico(p.to_str().unwrap(), "conteudo original").unwrap(); + std::fs::write(&p, "conteudo originalX").unwrap(); + assert!(carregar_verificado(p.to_str().unwrap()).is_err()); + assert!(!validar(p.to_str().unwrap())); + let _ = std::fs::remove_dir_all(&d); + } + + /// CP-3b — truncamento detectado pelo tamanho (determinístico). + #[test] + fn cp3b_truncamento_detectado() { + let d = tmp_dir("tr"); + let p = d.join("ck.json"); + salvar_atomico(p.to_str().unwrap(), "0123456789").unwrap(); + std::fs::write(&p, "0123").unwrap(); + assert!(carregar_verificado(p.to_str().unwrap()).is_err()); + let _ = std::fs::remove_dir_all(&d); + } + + /// CP-4a — manifesto ausente ⇒ recusa (estado legado é do `carregar`). + #[test] + fn cp4a_manifesto_ausente_recusado() { + let d = tmp_dir("leg"); + let p = d.join("ck.json"); + salvar_atomico(p.to_str().unwrap(), "x").unwrap(); + std::fs::remove_file(d.join("ck.json.manifest.json")).unwrap(); + assert!(carregar_verificado(p.to_str().unwrap()).is_err()); + let _ = std::fs::remove_dir_all(&d); + } + + /// CP-1b — falha de escrita PRESERVA o checkpoint anterior + /// (atomicidade prática: dir somente-leitura impede o create do tmp). + #[test] + fn cp1b_falha_preserva_anterior() { + let d = tmp_dir("fa"); + let p = d.join("ck.json"); + salvar_atomico(p.to_str().unwrap(), "estado_bom").unwrap(); + // dir somente-leitura: File::create(tmp) falha + let mut perms = std::fs::metadata(&d).unwrap().permissions(); + #[allow(clippy::unnecessary_cast)] + { + use std::os::unix::fs::PermissionsExt; + perms.set_mode(0o555); + } + std::fs::set_permissions(&d, perms).unwrap(); + let r = salvar_atomico(p.to_str().unwrap(), "estado_novo"); + let mut perms2 = std::fs::metadata(&d).unwrap().permissions(); + #[allow(clippy::unnecessary_cast)] + { + use std::os::unix::fs::PermissionsExt; + perms2.set_mode(0o755); + } + std::fs::set_permissions(&d, perms2).unwrap(); + assert!(r.is_err(), "escrita deveria falhar em dir somente-leitura"); + // o anterior permanece ÍNTEGRO e válido (Teo CP-1) + assert_eq!(carregar_verificado(p.to_str().unwrap()).unwrap(), "estado_bom"); + let _ = std::fs::remove_dir_all(&d); + } + + /// CP-5a — manifesto trocado entre arquivos (hash de outro) recusa. + #[test] + fn cp5a_manifesto_de_outro_recusado() { + let d = tmp_dir("sw"); + let a = d.join("a.json"); + let b = d.join("b.json"); + salvar_atomico(a.to_str().unwrap(), "AAAA").unwrap(); + salvar_atomico(b.to_str().unwrap(), "BBBB").unwrap(); + std::fs::copy(d.join("b.json.manifest.json"), d.join("a.json.manifest.json")).unwrap(); + assert!(carregar_verificado(a.to_str().unwrap()).is_err()); + let _ = std::fs::remove_dir_all(&d); + } +} diff --git a/src/fnh.rs b/src/fnh.rs index 2bfd8a431095fb0bc65c91f24703fdcca89cd9d8..f136c298c4c6910ad7fb074c793e21fd2856f4b9 100644 --- a/src/fnh.rs +++ b/src/fnh.rs @@ -570,6 +570,13 @@ impl GatePunicao { } } +impl crate::camadas::CamadaHomeostatica for GatePunicao { + fn gate_ativacao(&self, punicao: &crate::tipos::Probabilidade) + -> crate::tipos::GateAtivacao { + self.valor_tipado(punicao) + } +} + /// Camada FNH — uma hipótese flexível com rotações aprendíveis, suavização de /// canais e ativação flexível, GATADA pela punição do treino (r-28 §4.1). pub struct CamadaFNH { diff --git a/src/gerador.rs b/src/gerador.rs index 23fa9abfd52fa623f52ed9e5cc746ee845b5ab59..c1be8e22fbd3713f0dddb76e3af46e647980aa4d 100644 --- a/src/gerador.rs +++ b/src/gerador.rs @@ -246,18 +246,27 @@ impl GeradorConceitos { } let txt = serde_json::to_string(&serde_json::Value::Object(obj)) .map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?; - let tmp = format!("{caminho}.tmp"); - std::fs::write(&tmp, txt).map_err(|e| candle_core::Error::Msg(format!("io: {e}")))?; - std::fs::rename(&tmp, caminho).map_err(|e| candle_core::Error::Msg(format!("rename: {e}")))?; - Ok(()) + crate::checkpoint::salvar_atomico(caminho, &txt) + } + + /// Carrega com verificação de integridade (manifesto SHA-256 — v0.10.0). + /// Recusa checkpoints corrompidos/legados-sem-manifesto (Teo CP-3). + pub fn carregar_verificado(&mut self, caminho: &str) -> Result<()> { + let txt = crate::checkpoint::carregar_verificado(caminho)?; + self.carregar_de_txt(&txt) } /// Carrega um checkpoint salvo por `salvar` (mesma ordem de construção /// de `novo` ⇒ índices de variáveis casam 1-a-1 — ajuste fino honesto). + /// Caminho LEGADO: sem verificação de manifesto (compatibilidade v0.9.0). pub fn carregar(&mut self, caminho: &str) -> Result<()> { let txt = std::fs::read_to_string(caminho) .map_err(|e| candle_core::Error::Msg(format!("io: {e}")))?; - let obj: serde_json::Map = serde_json::from_str(&txt) + self.carregar_de_txt(&txt) + } + + fn carregar_de_txt(&mut self, txt: &str) -> Result<()> { + let obj: serde_json::Map = serde_json::from_str(txt) .map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?; let vars = self.vars(); for (i, v) in vars.iter().enumerate() { diff --git a/src/jepa.rs b/src/jepa.rs deleted file mode 100644 index ce79a1ca3950480f2a1650b50a2a7400e0792eb6..0000000000000000000000000000000000000000 --- a/src/jepa.rs +++ /dev/null @@ -1,1489 +0,0 @@ -//! Suíte JEPA v0.5.0 — núcleo unificado + 12 variantes + SonicMoE + ReplaySSM. -//! -//! **Núcleo (todas as variantes compartilham):** -//! - codificador `E_θ` (MLp SiLU), preditor `P_φ` (condicionado por posição/ -//! ação/edição), codificador-alvo EMA `E_ξ` com `ξ ← m·ξ + (1−m)·θ` -//! (gradiente cortado — alvo nunca recebe gradiente); -//! - regularizador variância-covariância (VICReg-lite) anti-colapso: -//! `V(z) = Σ_j max(0, γ − std(z_j))²` + fora-diagonal da covariância. -//! - perda: `L = Σ_b ‖P(z_ctx, pos_b) − sg(ẑ_b)‖²/B + λ_vc·V`. -//! -//! **Prova J-1 (impossibilidade de colapso, em r-20):** se todas as -//! incorporações colapsam para constante, `std(z_j) = 0 ∀j` ⇒ `V = d·γ² > 0`; -//! logo o colapso total NÃO é minimizador com `λ_vc > 0`. -//! -//! **Prova J-2 (EMA = stop-grad + ponto fixo):** `∂L/∂ξ = 0` (detach); se -//! `θ_t → θ*` então `ξ_t → θ*` (recursão linear contratante com `m < 1`). -//! -//! **Prova J-3 (decomposição de informação):** com `P` suficientemente -//! rico, `min_P L = Σ_b Var(ẑ_b | z_ctx)` — a perda retém só o componentes -//! previsíveis do alvo dado o contexto. -//! -//! **Variantes** (esquema de máscara + adaptador de modalidade): -//! Image-JEPA (blocos 2D) · V-JEPA 2 (tubos 3D + cond. de ação) · MC-JEPA -//! (cruz-modal, dois codificadores) · Audio-JEPA (bandas de espectrograma) · -//! Point-JEPA (grupos FPS) · 3D-JEPA (voxels) · Brain-JEPA (canal×tempo) · -//! T-JEPA (série temporal canal-independente) · LeJEPA (edição-condicionado) · -//! BJEPA (bidirecional com consistência de ciclo). -//! -//! **SonicMoE** — MoE com roteamento top-k, capacidade por especialista -//! (tile-aware) e perda auxiliar de balanceamento (argumento Switch: -//! `L_aux = E·Σ f_e·p_e ≥ 1` com igualdade sse uniforme). -//! -//! **ReplaySSM** — SSM de porta diagonal (`h_t = σ(a)⊙h_{t−1} + -//! (1−σ(a))⊙(Bx_t)`, estabilidade herdada do Teo S-2') + replay de experiências -//! para aprendizado continuado (interpolação de gradientes limita o -//! esquecimento — Teo R-1). - -use candle_core::{Device, Result, Tensor, Var}; -use crate::camadas::Linear; -use crate::rng::RngDeterministico; -use std::collections::HashMap; - -// --------------------------------------------------------------------------- -// Parte 1 — núcleo -// --------------------------------------------------------------------------- - -/// MLP 2 camadas com SiLU (d → h → d_saida). -pub struct MlpJepa { - pub l1: Linear, - pub l2: Linear, - pub d_entrada: usize, - pub d_saida: usize, -} - -impl MlpJepa { - pub fn novo(d_entrada: usize, d_oculta: usize, d_saida: usize, dev: &Device) -> Result { - let s1 = (1.0 / (d_entrada as f64).sqrt()) * 0.9; - let s2 = (1.0 / (d_oculta as f64).sqrt()) * 0.9; - Ok(Self { - l1: Linear::sem_vies(d_entrada, d_oculta, s1, dev)?, - l2: Linear::sem_vies(d_oculta, d_saida, s2, dev)?, - d_entrada, - d_saida, - }) - } - - pub fn fwd(&self, x: &Tensor) -> Result { - let h = candle_nn::ops::silu(&self.l1.fwd(x)?)?; - self.l2.fwd(&h) - } - - pub fn vars(&self) -> Vec { - [self.l1.vars(), self.l2.vars()].concat() - } - - /// EMA: `self ← m·self + (1−m)·outro` (usado no codificador-alvo). - pub fn ema_de(&mut self, outro: &MlpJepa, m: f64) -> Result<()> { - for (a, b) in self.vars().iter().zip(outro.vars().iter()) { - let novo = (a.as_tensor().affine(m, 0.0)? + b.as_tensor().affine(1.0 - m, 0.0)?)?; - a.set(&novo)?; - } - Ok(()) - } -} - -/// Núcleo JEPA: codificador + preditor + alvo EMA + regularizador VC. -pub struct NucleoJepa { - pub encoder: MlpJepa, - pub preditor: MlpJepa, - pub alvo: MlpJepa, - /// tabela de posições (blocos) — entrada extra do preditor. - pub pos: Var, - pub d_emb: usize, - pub d_cond: usize, - pub m: f64, - pub lambda_vc: f64, - pub gamma_vc: f64, -} - -impl NucleoJepa { - pub fn novo( - d_patch: usize, - d_emb: usize, - d_cond: usize, - n_pos: usize, - m: f64, - lambda_vc: f64, - gamma_vc: f64, - dev: &Device, - ) -> Result { - let encoder = MlpJepa::novo(d_patch, 2 * d_emb, d_emb, dev)?; - let preditor = MlpJepa::novo(d_emb + d_cond + d_emb, 2 * d_emb, d_emb, dev)?; - // alvo nasce IGUAL ao encoder (ponto de partida do EMA) - let mut alvo = MlpJepa::novo(d_patch, 2 * d_emb, d_emb, dev)?; - for (va, vb) in alvo.vars().iter().zip(encoder.vars().iter()) { - va.set(vb.as_tensor())?; - } - let pos_t = Tensor::randn(0f32, 0.02, (n_pos.max(1), d_emb), dev)?; - Ok(Self { - encoder, - preditor, - alvo, - pos: Var::from_tensor(&pos_t)?, - d_emb, - d_cond, - m, - lambda_vc, - gamma_vc, - }) - } - - pub fn vars(&self) -> Vec { - let mut v = self.encoder.vars(); - v.extend(self.preditor.vars()); - v.push(self.pos.clone()); - v // NOTA: alvo NÃO entra — é buffer EMA - } - - pub fn ema_passo(&mut self) -> Result<()> { - self.alvo.ema_de(&self.encoder, self.m) - } - - /// Codifica um conjunto de patches (n, d_patch) → (n, d_emb). - pub fn codificar(&self, patches: &Tensor) -> Result { - self.encoder.fwd(patches) - } - - /// Codifica com o alvo EMA — SEM gradiente (detach por construção: as - /// vars do alvo não estão no otimizador e o tensor é desconectado). - pub fn alvo_codificar(&self, patches: &Tensor) -> Result { - Ok(self.alvo.fwd(patches)?.detach()) - } - - /// Prediz o embedding do bloco `i_pos` a partir do contexto `z_ctx` - /// (d_emb) e da condição `cond` (d_cond; zeros se None). - pub fn predizer(&self, z_ctx: &Tensor, i_pos: usize, cond: Option<&Tensor>) -> Result { - // z_ctx: (b, d_emb) ou (d_emb,) - let b = z_ctx.dim(0).unwrap_or(1); - let dev = z_ctx.device(); - let pos = self.pos.as_tensor().get(i_pos.min(self.pos.dim(0)?.saturating_sub(1)))?; - let pos_b = pos.broadcast_as((b, self.d_emb))?; - let cond_b = match cond { - Some(c) => { - if c.dims().len() == 1 && b > 1 { - c.broadcast_as((b, self.d_cond))? - } else { - c.clone() - } - } - None => Tensor::zeros((b, self.d_cond), candle_core::DType::F32, dev)?, - }; - let entrada = Tensor::cat(&[z_ctx, &pos_b, &cond_b], candle_core::D::Minus1)?; - self.preditor.fwd(&entrada) - } - - /// Regularizador variância-covariância do lote de incorporações z (n, d). - /// - /// **J-1:** no colapso total `z_i = c ∀i` ⇒ `std_j = 0` ⇒ `V = d·γ²`. - pub fn perda_vc(&self, z: &Tensor) -> Result { - let n = z.dim(0)? as f64; - let d = self.d_emb; - let media = z.mean_keepdim(0)?; // (1, d) - let centr = z.broadcast_sub(&media)?; - let var = (centr.sqr()?.sum(0)? / n)?; // (d,) - let std = var.sqrt()?; - // dobradiça: max(0, γ − std)² - let gamma_t = - Tensor::new(self.gamma_vc as f32, z.device())?.broadcast_as(std.dims())?; - let gap = gamma_t.broadcast_sub(&std)?.clamp(0.0, f64::INFINITY)?; - let v = gap.sqr()?.sum_all()?; - // covariância fora-diagonal: C = centrᵀ·centr/n; perda = ‖C − diag‖²_F/(2d) - let cov = (centr.transpose(0, 1)?.matmul(¢r)? / n)?; // (d, d) - let mask_diag = { - let mut m = vec![0f32; d * d]; - for i in 0..d { - m[i * d + i] = 1.0; - } - Tensor::from_vec(m, (d, d), z.device())? - }; - let fora = ((&cov * &mask_diag)?.sqr()?.sum_all()? / (2.0 * d.max(1) as f64))?; - let v_norm = (v / d.max(1) as f64)?; - Ok((v_norm + fora)?) - } - - /// Perda JEPA padrão para UM lote: contexto (número variável de patches), - /// lista de blocos-alvo (posições), condição opcional. - /// - /// `z_ctx` = incorporação agregada do contexto (b, d_emb); - /// `alvos`: para cada elemento do lote, Vec<(i_pos, patches_alvo)>. - pub fn perda_lote( - &self, - z_ctx: &Tensor, - alvos: &[Vec<(usize, Tensor)>], - cond: Option<&Tensor>, - z_ctx_todos: &Tensor, - ) -> Result<(Tensor, f64, f64)> { - let b = z_ctx.dim(0)?; - let dev = z_ctx.device(); - let mut perdas: Vec = Vec::new(); - let mut n_alvos = 0usize; - for (i, blocos) in alvos.iter().enumerate() { - for (i_pos, patches_alvo) in blocos { - // ẑ_b = média das incorporações-alvo dos patches do bloco - let zt = self.alvo_codificar(patches_alvo)?; // (m, d_emb) - let zt_m = zt.mean(0)?; // (d_emb,) - let zt_b = zt_m.broadcast_as((1, self.d_emb))?; - let zc = z_ctx.get(i)?.broadcast_as((1, self.d_emb))?; - let c_i = cond.map(|c| c.get(i)?.broadcast_as((1, self.d_cond))).transpose()?; - let pred = self.predizer(&zc, *i_pos, c_i.as_ref())?; - let dif = (&pred - &zt_b)?; - let l = (dif.sqr()?.sum_all()? / self.d_emb as f64)?; - perdas.push(l); - n_alvos += 1; - } - } - let l_pred = if perdas.is_empty() { - Tensor::zeros((), candle_core::DType::F32, &dev)? - } else { - (Tensor::stack(&perdas, 0)?.sum_all()? / n_alvos.max(1) as f64)? - }; - let l_vc = self.perda_vc(z_ctx_todos)?; - let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(self.lambda_vc, 0.0)?)?; - let lp = l_pred.to_scalar::()? as f64; - let lv = l_vc.to_scalar::()? as f64; - Ok((total, lp, lv)) - } -} - -// --------------------------------------------------------------------------- -// Parte 2 — máscaras -// --------------------------------------------------------------------------- - -/// Máscara de blocos 2D (Image-JEPA): grade (h, w) de patches; sorteia -/// `n_blocos` retângulos aleatórios com fração alvo `frac` de patches. -pub fn mascara_blocos_2d(h: usize, w: usize, n_blocos: usize, frac: f64, rng: &mut RngDeterministico) -> Vec { - let total = h * w; - let alvo = (total as f64 * frac) as usize; - let mut masc = vec![false; total]; - let mut coberto = 0usize; - for _ in 0..n_blocos.max(1) { - if coberto >= alvo { - break; - } - let bh = 1 + rng.inteiro(h.min(4)); - let bw = 1 + rng.inteiro(w.min(4)); - let y0 = rng.inteiro(h + 1 - bh); - let x0 = rng.inteiro(w + 1 - bw); - for y in y0..y0 + bh { - for x in x0..x0 + bw { - let i = y * w + x; - if !masc[i] { - masc[i] = true; - coberto += 1; - } - } - } - } - masc -} - -/// Máscara de tubos 3D (V-JEPA 2): grade (t, h, w); tubos = retângulos que -/// atravessam o tempo. -pub fn mascara_tubos_3d(t: usize, h: usize, w: usize, n_tubos: usize, frac: f64, rng: &mut RngDeterministico) -> Vec { - let total = t * h * w; - let alvo = (total as f64 * frac) as usize; - let mut masc = vec![false; total]; - let mut coberto = 0usize; - for _ in 0..n_tubos.max(1) { - if coberto >= alvo { - break; - } - let bh = 1 + rng.inteiro(h.min(3)); - let bw = 1 + rng.inteiro(w.min(3)); - let y0 = rng.inteiro(h + 1 - bh); - let x0 = rng.inteiro(w + 1 - bw); - for tt in 0..t { - for y in y0..y0 + bh { - for x in x0..x0 + bw { - let i = (tt * h + y) * w + x; - if !masc[i] { - masc[i] = true; - coberto += 1; - } - } - } - } - } - masc -} - -/// Máscara de bandas (Audio-JEPA): grade (f, tau) — bandas de frequência -/// horizontais com extensão temporal. -pub fn mascara_bandas(f: usize, tau: usize, n_bandas: usize, frac: f64, rng: &mut RngDeterministico) -> Vec { - mascara_blocos_2d(f, tau, n_bandas, frac, rng) // blocos na grade (f, tau) -} - -/// Máscara de blocos 3D (3D-JEPA): grade (x, y, z) de voxels. -pub fn mascara_blocos_3d(x: usize, y: usize, z: usize, n_blocos: usize, frac: f64, rng: &mut RngDeterministico) -> Vec { - mascara_tubos_3d(x, y, z, n_blocos, frac, rng) -} - -/// Máscara de segmentos canal×tempo (Brain-JEPA): canais inteiros OU -/// janelas temporais (aleatório por chamada). -pub fn mascara_segmentos(c: usize, t: usize, frac: f64, rng: &mut RngDeterministico) -> Vec { - let total = c * t; - let alvo = (total as f64 * frac) as usize; - let mut masc = vec![false; total]; - if rng.inteiro(2) == 0 { - // canais inteiros - let mut coberto = 0; - let mut canais: Vec = (0..c).collect(); - // embaralhar - for i in (1..canais.len()).rev() { - let j = rng.inteiro(i + 1); - canais.swap(i, j); - } - for &ch in canais.iter() { - if coberto >= alvo { - break; - } - for tt in 0..t { - masc[ch * t + tt] = true; - coberto += 1; - } - } - } else { - // janelas temporais - let mut coberto = 0; - let mut t0 = 0; - while coberto < alvo && t0 < t { - let len = 1 + rng.inteiro((t / 4).max(1)); - for ch in 0..c { - for tt in t0..(t0 + len).min(t) { - masc[ch * t + tt] = true; - coberto += 1; - } - } - t0 += len + 1; - } - } - masc -} - -/// Máscara 1D (T-JEPA): intervalos contíguos em n posições. -pub fn mascara_intervalos_1d(n: usize, frac: f64, rng: &mut RngDeterministico) -> Vec { - let alvo = (n as f64 * frac) as usize; - let mut masc = vec![false; n]; - let mut coberto = 0; - while coberto < alvo { - let len = 1 + rng.inteiro((n / 8).max(1)); - let i0 = rng.inteiro(n); - for i in i0..(i0 + len).min(n) { - if !masc[i] { - masc[i] = true; - coberto += 1; - } - } - } - masc -} - -/// Amostragem dos pontos mais distantes (FPS) para Point-JEPA. -pub fn pontos_mais_distantes(pontos: &[Vec], k: usize, rng: &mut RngDeterministico) -> Vec { - let n = pontos.len(); - if n == 0 || k == 0 { - return vec![]; - } - let k = k.min(n); - let mut centros = vec![rng.inteiro(n)]; - let mut dist_min: Vec = pontos - .iter() - .map(|p| distancia_eucl(p, &pontos[centros[0]])) - .collect(); - while centros.len() < k { - let prox = dist_min - .iter() - .enumerate() - .filter(|(i, _)| !centros.contains(i)) - .max_by(|a, b| a.1.partial_cmp(b.1).unwrap()) - .map(|(i, _)| i) - .unwrap_or(0); - centros.push(prox); - for (i, p) in pontos.iter().enumerate() { - let d = distancia_eucl(p, &pontos[prox]); - if d < dist_min[i] { - dist_min[i] = d; - } - } - } - centros -} - -fn distancia_eucl(a: &[f64], b: &[f64]) -> f64 { - a.iter().zip(b).map(|(x, y)| (x - y) * (x - y)).sum::().sqrt() -} - -// --------------------------------------------------------------------------- -// Parte 3 — adaptadores de modalidade -// --------------------------------------------------------------------------- - -/// (C, H, W) → patches (H/ph · W/pw, C·ph·pw) — Image-JEPA. -pub fn em_patches_2d(img: &Tensor, ph: usize, pw: usize) -> Result { - let (c, h, w) = (img.dim(0)?, img.dim(1)?, img.dim(2)?); - let dev = img.device(); - let mut linhas: Vec = Vec::new(); - for y in (0..h).step_by(ph) { - for x in (0..w).step_by(pw) { - let hh = ph.min(h - y); - let ww = pw.min(w - x); - let patch = img.narrow(0, 0, c)?.narrow(1, y, hh)?.narrow(2, x, ww)?; - // padding para (C, ph, pw) se borda - let patch = if hh < ph || ww < pw { - let mut buf = patch.flatten_all()?.to_vec1::()?; - let falta = c * ph * pw - buf.len(); - buf.extend(std::iter::repeat(0f32).take(falta)); - Tensor::from_vec(buf, (c * ph * pw,), &dev)? - } else { - patch.flatten_all()? - }; - linhas.push(patch); - } - } - Ok(Tensor::stack(&linhas, 0)?) -} - -/// (T, H, W) → tubos (T/pt · H/ph · W/pw, pt·ph·pw) — V-JEPA 2. -pub fn em_tubos_3d(vid: &Tensor, pt: usize, ph: usize, pw: usize) -> Result { - let (t, h, w) = (vid.dim(0)?, vid.dim(1)?, vid.dim(2)?); - let dev = vid.device(); - let mut linhas: Vec = Vec::new(); - for tt in (0..t).step_by(pt) { - let tl = pt.min(t - tt); - for y in (0..h).step_by(ph) { - for x in (0..w).step_by(pw) { - let hh = ph.min(h - y); - let ww = pw.min(w - x); - let tubo = vid - .narrow(0, tt, tl)? - .narrow(1, y, hh)? - .narrow(2, x, ww)? - .flatten_all()?; - let mut buf = tubo.to_vec1::()?; - let falta = pt * ph * pw - buf.len(); - buf.extend(std::iter::repeat(0f32).take(falta)); - linhas.push(Tensor::from_vec(buf, (pt * ph * pw,), &dev)?); - } - } - } - Ok(Tensor::stack(&linhas, 0)?) -} - -/// (F, τ) → patches (F/pf · τ/pt, pf·pt) — Audio-JEPA. -pub fn em_patches_espectrograma(spec: &Tensor, pf: usize, pt: usize) -> Result { - let (f, tau) = (spec.dim(0)?, spec.dim(1)?); - let dev = spec.device(); - let mut linhas: Vec = Vec::new(); - for i in (0..f).step_by(pf) { - for j in (0..tau).step_by(pt) { - let ff = pf.min(f - i); - let tt = pt.min(tau - j); - let p = spec.narrow(0, i, ff)?.narrow(1, j, tt)?.flatten_all()?; - let mut buf = p.to_vec1::()?; - let falta = pf * pt - buf.len(); - buf.extend(std::iter::repeat(0f32).take(falta)); - linhas.push(Tensor::from_vec(buf, (pf * pt,), &dev)?); - } - } - Ok(Tensor::stack(&linhas, 0)?) -} - -/// (C, T) → patches canal-independentes (C · T/pt, pt) — T-JEPA/Brain-JEPA. -pub fn em_patches_serie(ts: &Tensor, pt: usize) -> Result { - let (c, t) = (ts.dim(0)?, ts.dim(1)?); - let dev = ts.device(); - let mut linhas: Vec = Vec::new(); - for ch in 0..c { - for j in (0..t).step_by(pt) { - let tt = pt.min(t - j); - let p = ts.narrow(0, ch, 1)?.narrow(1, j, tt)?.flatten_all()?; - let mut buf = p.to_vec1::()?; - let falta = pt - buf.len(); - buf.extend(std::iter::repeat(0f32).take(falta)); - linhas.push(Tensor::from_vec(buf, (pt,), &dev)?); - } - } - Ok(Tensor::stack(&linhas, 0)?) -} - -/// (X, Y, Z) → voxels (X/px · Y/py · Z/pz, px·py·pz) — 3D-JEPA. -pub fn em_voxels(vol: &Tensor, px: usize, py: usize, pz: usize) -> Result { - let (x, y, z) = (vol.dim(0)?, vol.dim(1)?, vol.dim(2)?); - let dev = vol.device(); - let mut linhas: Vec = Vec::new(); - for i in (0..x).step_by(px) { - for j in (0..y).step_by(py) { - for k in (0..z).step_by(pz) { - let lx = px.min(x - i); - let ly = py.min(y - j); - let lz = pz.min(z - k); - let p = vol.narrow(0, i, lx)?.narrow(1, j, ly)?.narrow(2, k, lz)?.flatten_all()?; - let mut buf = p.to_vec1::()?; - let falta = px * py * pz - buf.len(); - buf.extend(std::iter::repeat(0f32).take(falta)); - linhas.push(Tensor::from_vec(buf, (px * py * pz,), &dev)?); - } - } - } - Ok(Tensor::stack(&linhas, 0)?) -} - -/// Nuvem de pontos (n, d) → grupos FPS (k, m·d): k centros por FPS, cada -/// grupo = os m vizinhos mais próximos do centro (concatenados) — Point-JEPA. -pub fn em_grupos_pontos(pc: &Tensor, k: usize, m: usize, rng: &mut RngDeterministico) -> Result { - let n = pc.dim(0)?; - let d = pc.dim(1)?; - let coords = pc.to_vec2::()?; - let coords_f: Vec> = coords.iter().map(|r| r.iter().map(|&v| v as f64).collect()).collect(); - let centros = pontos_mais_distantes(&coords_f, k, rng); - let dev = pc.device(); - let mut linhas: Vec = Vec::new(); - for &ci in ¢ros { - // m vizinhos mais próximos do centro ci (incluindo ele próprio) - let mut ord: Vec = (0..n).collect(); - ord.sort_by(|&a, &b| { - let da = distancia_eucl(&coords_f[a], &coords_f[ci]); - let db = distancia_eucl(&coords_f[b], &coords_f[ci]); - da.partial_cmp(&db).unwrap() - }); - let mut buf: Vec = Vec::with_capacity(m * d); - for &idx in ord.iter().take(m) { - for j in 0..d { - buf.push(coords[idx][j]); - } - } - let falta = m * d - buf.len(); - buf.extend(std::iter::repeat(0f32).take(falta)); - linhas.push(Tensor::from_vec(buf, (m * d,), &dev)?); - } - Ok(Tensor::stack(&linhas, 0)?) -} - -// --------------------------------------------------------------------------- -// Parte 4 — variantes e treino unificado -// --------------------------------------------------------------------------- - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum VarianteJepa { - ImageJepa, - VJepa2, - McJepa, - AudioJepa, - PointJepa, - Jepa3D, - BrainJepa, - TJepa, - LeJepa, - BJepa, -} - -impl VarianteJepa { - pub fn nome(&self) -> &'static str { - match self { - Self::ImageJepa => "Image-JEPA", - Self::VJepa2 => "V-JEPA 2", - Self::McJepa => "MC-JEPA", - Self::AudioJepa => "Audio-JEPA", - Self::PointJepa => "Point-JEPA", - Self::Jepa3D => "3D-JEPA", - Self::BrainJepa => "Brain-JEPA", - Self::TJepa => "T-JEPA", - Self::LeJepa => "LeJEPA", - Self::BJepa => "BJEPA", - } - } -} - -#[derive(Debug, Clone)] -pub struct CfgJepa { - pub d_emb: usize, - pub epocas: usize, - pub lr: f64, - pub m: f64, // EMA - pub lambda_vc: f64, - pub gamma_vc: f64, - pub frac_mascara: f64, - pub semente: u64, - /// tamanho do bloco-alvo (nº de patches por bloco). - pub tam_bloco: usize, -} - -impl Default for CfgJepa { - fn default() -> Self { - Self { - d_emb: 32, - epocas: 60, - lr: 1.5e-3, - m: 0.99, - lambda_vc: 0.5, - gamma_vc: 1.0, - frac_mascara: 0.35, - semente: 42, - tam_bloco: 2, - } - } -} - -/// Relatório de treino de uma variante. -#[derive(Debug, Clone)] -pub struct RelatorioJepa { - pub variante: String, - pub perda_inicial: f64, - pub perda_final: f64, - /// perda PREDITIVA isolada (sem o regularizador VC) — critério de - /// aprendizado honesto (a VC pode crescer legitimamente ao empurrar o - /// std das incorporações para cima). - pub perda_pred_inicial: f64, - pub perda_pred_final: f64, - /// desvio-padrão médio por dimensão das incorporações (monitor de - /// colapso: → 0 = colapso). - pub std_emb_final: f64, - pub std_emb_inicial: f64, - pub n_patches: usize, - pub d_patch: usize, - pub epocas: usize, -} - -/// Treina uma variante JEPA sobre patches já extraídos `(n, d_patch)` com -/// um esquema de máscara fornecido (padrão: intervalos 1D). -/// -/// Máscara=true ⇒ patch é ALVO (bloco); false ⇒ CONTEXTO. -pub fn treinar_variante( - patches: &Tensor, - mascara: &dyn Fn(usize, &mut RngDeterministico) -> Vec, - cfg: &CfgJepa, - variante: VarianteJepa, - cond: Option<&Tensor>, - dev: &Device, -) -> Result { - let n = patches.dim(0)?; - let d_patch = patches.dim(1)?; - let mut rng = RngDeterministico::novo(cfg.semente); - let mut nucleo = NucleoJepa::novo( - d_patch, - cfg.d_emb, - if cond.is_some() { cond.unwrap().dim(1)? } else { 0 }, - n, - cfg.m, - cfg.lambda_vc, - cfg.gamma_vc, - dev, - )?; - let otimo_vars = nucleo.vars(); - let mut otimizador = crate::treino::AdamWManual::novo(otimo_vars, cfg.lr, 0.01); - - // métricas iniciais - let z0 = nucleo.codificar(patches)?; - let std0 = std_por_dim(&z0)?; - - let mut perda_inicial = f64::NAN; - let mut perda_final = f64::NAN; - let mut pred_inicial = f64::NAN; - let mut pred_final = f64::NAN; - let mut vc_final = f64::NAN; - for epoca in 0..cfg.epocas { - // máscara fresca a cada época (com proteção contra máscara degenerada: - // se contexto ou alvo ficar vazio, repete com fração menor — a época - // NUNCA é pulada, a perda sempre é computada) - let (ctx_idx, blocos) = loop { - let mut masc = mascara(n, &mut rng); - // defensivo: máscara menor que n => o resto é contexto - if masc.len() < n { - masc.resize(n, false); - } - masc.truncate(n); - let ctx: Vec = (0..n).filter(|&i| !masc[i]).map(|i| i as u32).collect(); - let alvos: Vec = (0..n).filter(|&i| masc[i]).collect(); - if !ctx.is_empty() && !alvos.is_empty() { - break (ctx, alvos); - } - }; - // agrupa alvos em blocos de tam_bloco - let grupos: Vec> = blocos - .chunks(cfg.tam_bloco.max(1)) - .map(|c| c.to_vec()) - .collect(); - if ctx_idx.is_empty() || grupos.is_empty() { - continue; - } - let dev2 = patches.device(); - let n_ctx = ctx_idx.len(); - let idx_t = Tensor::from_vec(ctx_idx, (n_ctx,), &dev2)?; - let ctx_patches = patches.index_select(&idx_t, 0)?; - let z_ctx_todos = nucleo.codificar(&ctx_patches)?; // (n_ctx, d_emb) - let z_ctx = z_ctx_todos.mean_keepdim(0)?; // (1, d_emb) - // alvos por grupo - let mut alvos: Vec> = Vec::new(); - for g in &grupos { - let idx: Vec = g.iter().map(|&i| i as u32).collect(); - let n_g = idx.len(); - let it = Tensor::from_vec(idx, (n_g,), &dev2)?; - alvos.push(vec![(g[0], patches.index_select(&it, 0)?)]); - } - // condição por amostra — broadcast (o lote é 1 aqui: o conjunto todo) - let (perda, lp, lv) = nucleo.perda_lote(&z_ctx, &[alvos_concat(&alvos)], cond, &z_ctx_todos)?; - let p = perda.to_scalar::()? as f64; - if p.is_finite() { - if epoca == 0 { - perda_inicial = p; - pred_inicial = lp; - } - perda_final = p; - pred_final = lp; - vc_final = lv; - } - // decaimento cosseno do lr + clipping de gradiente (estabilidade) - let frac = epoca as f64 / cfg.epocas.max(1) as f64; - let lr_t = cfg.lr * 0.5 * (1.0 + (std::f64::consts::PI * frac).cos()); - otimizador.set_lr(lr_t); - let mut grads = crate::treino::grads_achatados(&perda, &nucleo.vars())?; - crate::treino::AdamWManual::clipar(&mut grads, 1.0); - otimizador.passo(grads); - nucleo.ema_passo()?; - } - let z1 = nucleo.codificar(patches)?; - let std1 = std_por_dim(&z1)?; - let _ = (pred_inicial, vc_final); - Ok(RelatorioJepa { - variante: variante.nome().to_string(), - perda_inicial, - perda_final, - perda_pred_inicial: pred_inicial, - perda_pred_final: pred_final, - std_emb_final: std1, - std_emb_inicial: std0, - n_patches: n, - d_patch, - epocas: cfg.epocas, - }) -} - -fn alvos_concat(alvos: &[Vec<(usize, Tensor)>]) -> Vec<(usize, Tensor)> { - alvos.iter().flatten().cloned().collect() -} - -fn std_por_dim(z: &Tensor) -> Result { - let n = z.dim(0)? as f64; - let media = z.mean_keepdim(0)?; - let centr = z.broadcast_sub(&media)?; - let var = (centr.sqr()?.sum(0)? / n)?; - Ok(var.sqrt()?.mean_all()?.to_scalar::()? as f64) -} - -// --------------------------------------------------------------------------- -// Parte 5 — SonicMoE (IO/tile-aware) -// --------------------------------------------------------------------------- - -/// MoE top-k com capacidade por especialista e perda de balanceamento. -/// -/// **Prova SM-1 (limite de capacidade):** o roteamento tile-aware mantém no -/// máximo `C` tokens por especialista por ladrilho — o drop é determinístico -/// (ordem de chegada) e o nº de tokens descartados é `Σ_e max(0, load_e − C)`. -/// -/// **Prova SM-2 (balanceamento):** `L_aux = E·Σ_e f_e·p_e`; com roteamento -/// auto-consistente (`f = p`), `L_aux = E·Σ p_e² ≥ 1` com igualdade sse -/// `p_e = 1/E` (Cauchy–Schchwarz) — o mínimo força o roteador uniforme. -pub struct SonicMoe { - pub roteador: Linear, // d → E - pub especialistas: Vec, // E experts d → d - pub n_top: usize, - pub capacidade: usize, - pub lambda_aux: f64, -} - -pub struct SaidaMoe { - pub saida: Tensor, - pub perda_aux: Tensor, - pub carga: Vec, // tokens efetivamente processados por especialista - pub descartados: usize, // tokens com especialistas cortados pela capacidade -} - -impl SonicMoe { - pub fn novo(d: usize, n_expertos: usize, n_top: usize, capacidade: usize, dev: &Device) -> Result { - let roteador = Linear::sem_vies(d, n_expertos, 0.1, dev)?; - let especialistas = (0..n_expertos) - .map(|_| MlpJepa::novo(d, 2 * d, d, dev)) - .collect::>>()?; - Ok(Self { roteador, especialistas, n_top, capacidade, lambda_aux: 0.01 }) - } - - pub fn vars(&self) -> Vec { - let mut v = self.roteador.vars(); - for e in &self.especialistas { - v.extend(e.vars()); - } - v - } - - /// Forward tile-aware: (n, d) → (n, d). - pub fn fwd(&self, x: &Tensor) -> Result { - let n = x.dim(0)?; - let d = x.dim(1)?; - let e = self.especialistas.len(); - let dev = x.device(); - let logits = self.roteador.fwd(x)?; // (n, E) - let probs = candle_nn::ops::softmax(&logits, candle_core::D::Minus1)?; - - // top-k por token (não diferenciável — pesos do top-k são dif. via probs) - let probs_v = probs.to_vec2::()?; - let mut carga = vec![0usize; e]; - let mut descartados = 0usize; - // acumulador de saída (n, d) — construído por soma de contribuições - let mut saida = Tensor::zeros((n, d), candle_core::DType::F32, &dev)?; - let mut peso_total = Tensor::zeros((n, 1), candle_core::DType::F32, &dev)?; - // por especialista (tile): tokens cujo top-k contém e e com carga < C - for ei in 0..e { - // candidatos: tokens ordenados por prob do especialista ei - let mut cand: Vec<(usize, f32)> = (0..n) - .map(|i| (i, probs_v[i][ei])) - .collect(); - cand.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); - let mut escolhidos: Vec = Vec::new(); - for (i, _) in cand { - if carga[ei] >= self.capacidade { - break; - } - // verifica se ei está no top-k do token i - let mut ordem: Vec<(f32, usize)> = - (0..e).map(|k| (probs_v[i][k], k)).collect(); - ordem.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); - let top: Vec = ordem.iter().rev().take(self.n_top).map(|(_, k)| *k).collect(); - if top.contains(&ei) { - escolhidos.push(i); - carga[ei] += 1; - } - } - if escolhidos.is_empty() { - continue; - } - // tile do especialista: uma única passagem em lote - let idx: Vec = escolhidos.iter().map(|&i| i as u32).collect(); - let n_e = idx.len(); - let idx_t = Tensor::from_vec(idx, (n_e,), &dev)?; - let tile = x.index_select(&idx_t, 0)?; - let y = self.especialistas[ei].fwd(&tile)?; // (m, d) - // pesos: prob do especialista ei para cada token escolhido - let w: Vec = escolhidos.iter().map(|&i| probs_v[i][ei]).collect(); - let n_w = escolhidos.len(); - let w_t = Tensor::from_vec(w.clone(), (n_w, 1), &dev)?; - let contrib = y.broadcast_mul(&w_t)?; - // scatter-add: acumula nas posições - let mut buf = saida.to_vec2::()?; - let contrib_v = contrib.to_vec2::()?; - for (k, &i) in escolhidos.iter().enumerate() { - for j in 0..d { - buf[i][j] += contrib_v[k][j]; - } - } - saida = Tensor::from_vec( - buf.into_iter().flatten().collect::>(), - (n, d), - &dev, - )?; - let mut pt = peso_total.to_vec2::()?; - for (k, &i) in escolhidos.iter().enumerate() { - pt[i][0] += w[k]; - } - peso_total = Tensor::from_vec( - pt.into_iter().flatten().collect::>(), - (n, 1), - &dev, - )?; - } - // tokens sem especialista (todos cortados) → descartados; saída 0 - let pt_v = peso_total.to_vec2::()?; - for i in 0..n { - if pt_v[i][0] == 0.0 { - descartados += 1; - } - } - // renormaliza pelo peso efetivo (evita viés da capacidade) - let denom = peso_total.clamp(1e-6, f64::INFINITY)?; - let saida = saida.broadcast_div(&denom)?; - - // perda auxiliar: E·Σ f_e·p_e (f = fração de tokens cujo top-1 é e) - let mut f = vec![0f32; e]; - for i in 0..n { - let (mut melhor, mut mp) = (0usize, f32::NEG_INFINITY); - for k in 0..e { - if probs_v[i][k] > mp { - mp = probs_v[i][k]; - melhor = k; - } - } - f[melhor] += 1.0 / n as f32; - } - let f_t = Tensor::from_vec(f, (e,), &dev)?; - let p_media = probs.mean(0)?; // (E,) - let perda_aux = ((&f_t * &p_media)?.sum_all()? * (e as f64))?; - Ok(SaidaMoe { saida, perda_aux, carga, descartados }) - } -} - -// --------------------------------------------------------------------------- -// Parte 6 — ReplaySSM -// --------------------------------------------------------------------------- - -/// SSM de porta diagonal com replay para aprendizado continuado. -/// -/// Recorrência: `h_t = σ(a) ⊙ h_{t−1} + (1 − σ(a)) ⊙ (B x_t)`. -/// -/// **Prova R-1 (estabilidade):** `|σ(a)| < 1` ⇒ perturbações de `h_0` -/// decaem geometricamente (mesma estrutura do Teo S-2' do SSD-Δ). -/// -/// **Prova R-2 (limite de esquecimento):** o passo de treino mistura -/// gradientes `(1−α)·∇L_novo + α·∇L_replay`; para `L_antiga` μ-fortemente -/// convexa com gradientes novos limitados por `G`, o afastamento do ótimo -/// antigo por passo é `≤ (1−α)·η·G` (sem replay: `≤ η·G`) — o replay -/// contrai o drift por um fator `1/(1−α)` no regime linear. -pub struct ReplaySsm { - pub a: Var, // (d,) — porta (sigmoid no uso) - pub b: Var, // (d_in, d) — projeção de entrada - pub d: usize, - pub alpha_replay: f64, - pub buffer: Vec, - pub capacidade_buffer: usize, -} - -impl ReplaySsm { - pub fn novo(d_in: usize, d: usize, alpha_replay: f64, capacidade_buffer: usize, dev: &Device) -> Result { - let a_t = Tensor::randn(0f32, 0.5, (d,), dev)?; - let b_t = Tensor::randn(0f32, 0.1, (d_in, d), dev)?; - Ok(Self { - a: Var::from_tensor(&a_t)?, - b: Var::from_tensor(&b_t)?, - d, - alpha_replay, - buffer: Vec::new(), - capacidade_buffer, - }) - } - - pub fn vars(&self) -> Vec { - vec![self.a.clone(), self.b.clone()] - } - - /// Scan: x (T, d_in) → h (T, d). Diferenciável (candle autograd). - pub fn scan(&self, x: &Tensor) -> Result { - let t = x.dim(0)?; - let dev = x.device(); - let porta = candle_nn::ops::sigmoid(&self.a.as_tensor().broadcast_as((1, self.d))?)?; // (1, d) - let um_menos = porta.affine(-1.0, 1.0)?; // 1 − σ(a) - let entrada = x.matmul(&self.b.as_tensor())?; // (T, d) - let mut h = Tensor::zeros((1, self.d), candle_core::DType::F32, &dev)?; - let mut saidas: Vec = Vec::with_capacity(t); - for i in 0..t { - let xt = entrada.narrow(0, i, 1)?; - h = ((&porta * &h)? + (&um_menos * &xt)?)?; - saidas.push(h.clone()); - } - Ok(Tensor::stack(&saidas, 0)?) - } - - /// Empurra uma janela de treino no buffer (FIFO). - pub fn guardar(&mut self, x: &Tensor) { - if self.buffer.len() >= self.capacidade_buffer { - self.buffer.remove(0); - } - self.buffer.push(x.detach()); - } - - /// Um passo de treino no lote novo (com mistura de replay). - /// Devolve (perda_total, perda_novo, perda_replay). - pub fn passo_treino( - &mut self, - x: &Tensor, - alvo: &Tensor, - otimizador: &mut crate::treino::AdamWManual, - ) -> Result<(f64, f64, f64)> { - // perda nova - let h = self.scan(x)?; - let dif = (&h - alvo)?; - let perda_novo = dif.sqr()?.mean_all()?; - // replay - let mut perda_total = perda_novo.clone(); - let mut p_r = 0.0; - if !self.buffer.is_empty() { - let idx = self.buffer.len() - 1; - let antigo = self.buffer[idx].clone(); - let hr = self.scan(&antigo)?; - // alvo do replay: manter a dinâmica aprendida estável (magnitudes - // finitas) — auto-supervisão de estabilidade - let dr = hr.sqr()?.mean_all()?; - perda_total = (perda_total.affine(1.0 - self.alpha_replay, 0.0)? - + dr.affine(self.alpha_replay, 0.0)?)?; - p_r = dr.to_scalar::()? as f64; - } - let pn = perda_novo.to_scalar::()? as f64; - otimizador.passo(crate::treino::grads_achatados(&perda_total, &self.vars())?); - self.guardar(x); - let pt = perda_total.to_scalar::()? as f64; - Ok((pt, pn, p_r)) - } -} - -// --------------------------------------------------------------------------- -// Variantes especiais: LeJEPA (edição-condicionado), MC-JEPA (cruz-modal), -// BJEPA (bidirecional com consistência de ciclo) -// --------------------------------------------------------------------------- - -/// **LeJEPA** — predição latente condicionada por EDIÇÃO: contexto = E(x), -/// condição = instrução de edição `e` (vetor de baixa dimensão), alvo = -/// `E_ξ(edit_e(x))`. A condição é a INSTRUÇÃO (não o resultado — sem -/// vazamento: `e` não contém `edit(x)`). -pub fn treinar_lejepa( - dados: &Tensor, // (n, d_patch) — originais - edits: &Tensor, // (n, d_cond) — instruções de edição - editados: &Tensor, // (n, d_patch) — resultados das edições - cfg: &CfgJepa, - dev: &Device, -) -> Result { - let n = dados.dim(0)?; - let d_patch = dados.dim(1)?; - let d_cond = edits.dim(1)?; - let mut rng = RngDeterministico::novo(cfg.semente); - let mut nucleo = NucleoJepa::novo( - d_patch, cfg.d_emb, d_cond, 1, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev, - )?; - let mut otim = crate::treino::AdamWManual::novo(nucleo.vars(), cfg.lr, 0.01); - let mut perda_inicial = f64::NAN; - let mut perda_final = f64::NAN; - let z0 = nucleo.codificar(dados)?; - let std0 = std_por_dim(&z0)?; - for epoca in 0..cfg.epocas { - // subamostra do lote - let idx = rng.amostra_k_de_n(n.min(64), n); - let sel: Vec = idx.iter().map(|&i| i as u32).collect(); - let it = Tensor::from_vec(sel.clone(), (sel.len(),), dev)?; - let xb = dados.index_select(&it, 0)?; - let eb = edits.index_select(&it, 0)?; - let yb = editados.index_select(&it, 0)?; - let z_ctx = nucleo.codificar(&xb)?; // (m, d_emb) - let mut perdas: Vec = Vec::new(); - for i in 0..xb.dim(0)? { - let zc = z_ctx.get(i)?.broadcast_as((1, cfg.d_emb))?; - let e_i = eb.get(i)?.broadcast_as((1, d_cond))?; - let yt = yb.get(i)?.broadcast_as((1, d_patch))?; - let zt = nucleo - .alvo_codificar(&yt)? - .mean(0)? - .broadcast_as((1, cfg.d_emb))?; - let pred = nucleo.predizer(&zc, 0, Some(&e_i))?; - let dif = (&pred - &zt)?; - perdas.push((dif.sqr()?.sum_all()? / cfg.d_emb as f64)?); - } - let l_pred = (Tensor::stack(&perdas, 0)?.sum_all()? / perdas.len() as f64)?; - let l_vc = nucleo.perda_vc(&z_ctx)?; - let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(cfg.lambda_vc, 0.0)?)?; - let p = total.to_scalar::()? as f64; - if p.is_finite() { - if epoca == 0 { - perda_inicial = p; - } - perda_final = p; - } - let mut grads = crate::treino::grads_achatados(&total, &nucleo.vars())?; - crate::treino::AdamWManual::clipar(&mut grads, 1.0); - otim.passo(grads); - nucleo.ema_passo()?; - } - let z1 = nucleo.codificar(dados)?; - Ok(RelatorioJepa { - variante: "LeJEPA".into(), - perda_inicial, - perda_final, - perda_pred_inicial: perda_inicial, - perda_pred_final: perda_final, - std_emb_final: std_por_dim(&z1)?, - std_emb_inicial: std0, - n_patches: n, - d_patch, - epocas: cfg.epocas, - }) -} - -/// **MC-JEPA** — cruz-modal: dois codificadores, predição nos DOIS sentidos -/// (A→B e B→A) com preditores separados e alvos EMA dos dois lados. -pub fn treinar_mcjepa( - mod_a: &Tensor, // (n, d_a) - mod_b: &Tensor, // (n, d_b) - cfg: &CfgJepa, - dev: &Device, -) -> Result { - let n = mod_a.dim(0)?; - let d_a = mod_a.dim(1)?; - let d_b = mod_b.dim(1)?; - // núcleo A→B e núcleo B→A (compartilham apenas a estrutura) - let mut nb = NucleoJepa::novo(d_a, cfg.d_emb, 0, 1, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev)?; - let mut na = NucleoJepa::novo(d_b, cfg.d_emb, 0, 1, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev)?; - let mut otim = crate::treino::AdamWManual::novo( - [nb.vars(), na.vars()].concat(), - cfg.lr, - 0.01, - ); - let mut rng = RngDeterministico::novo(cfg.semente); - let mut perda_inicial = f64::NAN; - let mut perda_final = f64::NAN; - for epoca in 0..cfg.epocas { - let idx = rng.amostra_k_de_n(n.min(64), n); - let sel: Vec = idx.iter().map(|&i| i as u32).collect(); - let it = Tensor::from_vec(sel, (idx.len(),), dev)?; - let xa = mod_a.index_select(&it, 0)?; - let xb = mod_b.index_select(&it, 0)?; - // z_a = E_a(x_a) prevê ẑ_b; z_b = E_b(x_b) prevê ẑ_a - let z_a = nb.codificar(&xa)?; // (m, d_emb) - let z_b = na.codificar(&xb)?; - let t_a = na.alvo_codificar(&xa)?; // alvo EMA do lado A - let t_b = nb.alvo_codificar(&xb)?; // alvo EMA do lado B - let m_ = xa.dim(0)?; - let mut perdas: Vec = Vec::new(); - for i in 0..m_ { - // A → B - let zc = z_a.get(i)?.broadcast_as((1, cfg.d_emb))?; - let zt = t_b.get(i)?.broadcast_as((1, cfg.d_emb))?; - let pred = nb.predizer(&zc, 0, None)?; - let dif = (&pred - &zt)?; - perdas.push((dif.sqr()?.sum_all()? / cfg.d_emb as f64)?); - // B → A - let zc2 = z_b.get(i)?.broadcast_as((1, cfg.d_emb))?; - let zt2 = t_a.get(i)?.broadcast_as((1, cfg.d_emb))?; - let pred2 = na.predizer(&zc2, 0, None)?; - let dif2 = (&pred2 - &zt2)?; - perdas.push((dif2.sqr()?.sum_all()? / cfg.d_emb as f64)?); - } - let l_pred = (Tensor::stack(&perdas, 0)?.sum_all()? / perdas.len() as f64)?; - let l_vc = (nb.perda_vc(&z_a)? + na.perda_vc(&z_b)?)?; - let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(cfg.lambda_vc, 0.0)?)?; - let p = total.to_scalar::()? as f64; - if p.is_finite() { - if epoca == 0 { - perda_inicial = p; - } - perda_final = p; - } - let mut grads = crate::treino::grads_achatados(&total, &[nb.vars(), na.vars()].concat())?; - crate::treino::AdamWManual::clipar(&mut grads, 1.0); - otim.passo(grads); - nb.ema_passo()?; - na.ema_passo()?; - } - let z1 = nb.codificar(mod_a)?; - Ok(RelatorioJepa { - variante: "MC-JEPA".into(), - perda_inicial, - perda_final, - perda_pred_inicial: perda_inicial, - perda_pred_final: perda_final, - std_emb_final: std_por_dim(&z1)?, - std_emb_inicial: std_por_dim(&nb.codificar(mod_a)?)?, - n_patches: n, - d_patch: d_a.max(d_b), - epocas: cfg.epocas, - }) -} - -/// **BJEPA** — bidirecional: preditor causal (z_t → z̃_{t+1}), anti-causal -/// (z_{t+1} → z̃_t) e consistência de ciclo -/// `‖P_b(P_f(z_t)) − sg(z_t)‖²` — a volta deve reconstruir o presente. -pub fn treinar_bjepa(patches: &Tensor, cfg: &CfgJepa, dev: &Device) -> Result { - let n = patches.dim(0)?; - let d_patch = patches.dim(1)?; - let mut nucleo = NucleoJepa::novo( - d_patch, cfg.d_emb, 0, 2, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev, - )?; - // preditor anti-causal: núcleo separado - let mut ninv = NucleoJepa::novo( - d_patch, cfg.d_emb, 0, 2, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev, - )?; - let mut otim = - crate::treino::AdamWManual::novo([nucleo.vars(), ninv.vars()].concat(), cfg.lr, 0.01); - let mut perda_inicial = f64::NAN; - let mut perda_final = f64::NAN; - for epoca in 0..cfg.epocas { - let z = nucleo.codificar(patches)?; // (n, d_emb) - let zt = nucleo.alvo_codificar(patches)?; // alvos EMA (stop-grad) - let mut perdas: Vec = Vec::new(); - for t in 0..n.saturating_sub(1) { - let zc = z.get(t)?.broadcast_as((1, cfg.d_emb))?; - let z_prox = zt.get(t + 1)?.broadcast_as((1, cfg.d_emb))?; - // causal: z_t → z̃_{t+1} (pos 0 = "próximo") - let pf = nucleo.predizer(&zc, 0, None)?; - let dif = (&pf - &z_prox)?; - perdas.push((dif.sqr()?.sum_all()? / cfg.d_emb as f64)?); - // anti-causal: z_{t+1} → z̃_t (pos 1 = "anterior") - let zb = z.get(t + 1)?.broadcast_as((1, cfg.d_emb))?; - let z_ant = zt.get(t)?.broadcast_as((1, cfg.d_emb))?; - let pb = ninv.predizer(&zb, 1, None)?; - let dif2 = (&pb - &z_ant)?; - perdas.push((dif2.sqr()?.sum_all()? / cfg.d_emb as f64)?); - // ciclo: P_b(P_f(z_t)) ≈ sg(z_t) - let pf_sg = nucleo.predizer(&zc, 0, None)?.detach(); - let pciclo = ninv.predizer(&pf_sg.broadcast_as((1, cfg.d_emb))?, 1, None)?; - let zt_sg = zt.get(t)?.broadcast_as((1, cfg.d_emb))?; - let dif3 = (&pciclo - &zt_sg)?; - perdas.push((dif3.sqr()?.sum_all()? / cfg.d_emb as f64)?); - } - let l_pred = (Tensor::stack(&perdas, 0)?.sum_all()? / perdas.len().max(1) as f64)?; - let l_vc = nucleo.perda_vc(&z)?; - let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(cfg.lambda_vc, 0.0)?)?; - let p = total.to_scalar::()? as f64; - if p.is_finite() { - if epoca == 0 { - perda_inicial = p; - } - perda_final = p; - } - let mut grads = crate::treino::grads_achatados(&total, &[nucleo.vars(), ninv.vars()].concat())?; - crate::treino::AdamWManual::clipar(&mut grads, 1.0); - otim.passo(grads); - nucleo.ema_passo()?; - ninv.ema_passo()?; - } - let z1 = nucleo.codificar(patches)?; - Ok(RelatorioJepa { - variante: "BJEPA".into(), - perda_inicial, - perda_final, - perda_pred_inicial: perda_inicial, - perda_pred_final: perda_final, - std_emb_final: std_por_dim(&z1)?, - std_emb_inicial: std_por_dim(&nucleo.codificar(patches)?)?, - n_patches: n, - d_patch, - epocas: cfg.epocas, - }) -} - -/// Esquema de máscara por variante a partir da grade de patches. -pub fn mascara_por_variante( - variante: VarianteJepa, - grid: &[usize], - frac: f64, - rng: &mut RngDeterministico, -) -> Vec { - match variante { - VarianteJepa::ImageJepa => mascara_blocos_2d(grid[0], grid.get(1).copied().unwrap_or(1), 4, frac, rng), - VarianteJepa::VJepa2 | VarianteJepa::Jepa3D => mascara_tubos_3d( - grid[0], - grid.get(1).copied().unwrap_or(1), - grid.get(2).copied().unwrap_or(1), - 3, - frac, - rng, - ), - VarianteJepa::AudioJepa => mascara_bandas(grid[0], grid.get(1).copied().unwrap_or(1), 3, frac, rng), - VarianteJepa::BrainJepa => mascara_segmentos(grid[0], grid.get(1).copied().unwrap_or(1), frac, rng), - _ => mascara_intervalos_1d(grid.iter().product::().max(1), frac, rng), - } -} - -pub fn versao() -> &'static str { - "jepa-0.5.0" -} - -#[cfg(test)] -mod testes { - use super::*; - - fn tensor_2d(dados: &[Vec], dev: &Device) -> Tensor { - let flat: Vec = dados.iter().flat_map(|v| v.iter().map(|&x| x as f32)).collect(); - Tensor::from_vec(flat, (dados.len(), dados[0].len()), dev).unwrap() - } - - #[test] - fn teste_mascaras_validas() { - // fração respeitada ±, particionamento contexto/alvo não-vazio - let mut rng = RngDeterministico::novo(1); - let m2 = mascara_blocos_2d(8, 8, 4, 0.3, &mut rng); - assert_eq!(m2.len(), 64); - let frac = m2.iter().filter(|&&b| b).count() as f64 / 64.0; - assert!(frac >= 0.15 && frac <= 0.55, "frac 2d = {frac}"); - assert!(m2.iter().any(|&b| !b), "contexto vazio"); - - let m3 = mascara_tubos_3d(4, 6, 6, 3, 0.3, &mut rng); - assert_eq!(m3.len(), 144); - let f3 = m3.iter().filter(|&&b| b).count() as f64 / 144.0; - assert!(f3 >= 0.15 && f3 <= 0.6, "frac 3d = {f3}"); - - let ms = mascara_segmentos(4, 32, 0.25, &mut rng); - assert_eq!(ms.len(), 128); - assert!(ms.iter().any(|&b| b) && ms.iter().any(|&b| !b)); - - let m1 = mascara_intervalos_1d(64, 0.3, &mut rng); - let f1 = m1.iter().filter(|&&b| b).count() as f64 / 64.0; - assert!(f1 >= 0.25 && f1 <= 0.45, "frac 1d = {f1}"); - } - - #[test] - fn teste_fps_cobre() { - let mut rng = RngDeterministico::novo(2); - let pontos: Vec> = (0..40) - .map(|i| { - let a = i as f64 * 0.1; - vec![a.cos() * 3.0, a.sin() * 3.0, (i % 7) as f64 * 0.3] - }) - .collect(); - let centros = pontos_mais_distantes(&pontos, 6, &mut rng); - assert_eq!(centros.len(), 6); - // centros distintos - let mut s = centros.clone(); - s.sort(); - s.dedup(); - assert_eq!(s.len(), 6); - } - - #[test] - fn teste_j1_colapso_impossivel() { - // No colapso total (z constante), a perda VC = γ² > 0 — verificando a - // fórmula implementada com um lote constante. - let dev = Device::Cpu; - let nucleo = NucleoJepa::novo(4, 8, 0, 4, 0.99, 1.0, 1.0, &dev).unwrap(); - let z = Tensor::from_vec(vec![0.5f32; 8 * 8], (8, 8), &dev).unwrap(); - let lvc = nucleo.perda_vc(&z).unwrap(); - let v = lvc.to_scalar::().unwrap() as f64; - // std=0 ⇒ dobradiça = γ = 1 ⇒ V/d = 1; cov = 0 - assert!(v > 0.9, "perda VC no colapso = {v} (deveria ser ~1)"); - } - - #[test] - fn teste_j2_ema_ponto_fixo() { - // encoder congelado ⇒ após muitas passadas EMA, alvo = encoder - let dev = Device::Cpu; - let mut nucleo = NucleoJepa::novo(4, 8, 0, 4, 0.5, 0.1, 1.0, &dev).unwrap(); - for _ in 0..40 { - nucleo.ema_passo().unwrap(); - } - let a = nucleo.alvo.vars()[0].as_tensor().flatten_all().unwrap().to_vec1::().unwrap(); - let b = nucleo.encoder.vars()[0].as_tensor().flatten_all().unwrap().to_vec1::().unwrap(); - let dif: f32 = a.iter().zip(&b).map(|(x, y)| (x - y).abs()).fold(0.0, f32::max); - assert!(dif < 1e-5, "EMA não convergiu: dif máx = {dif}"); - } - - #[test] - fn teste_jepa_aprende_latente_previsivel() { - // J-3: com alvo previsível (função determinística do contexto), a - // perda pred must cair substancialmente e o std das incorporações - // NÃO colapsa (J-1). - let dev = Device::Cpu; - let mut rng = RngDeterministico::novo(3); - // patches = 64 vetores 8-D em 4 grupos (estrutura previsível) - let mut dados: Vec> = Vec::new(); - for g in 0..4 { - let c = g as f64 * 2.0; - for _ in 0..16 { - dados.push((0..8).map(|k| c + rng.normal(0.0, 0.3) + (k as f64) * 0.05).collect()); - } - } - let patches = tensor_2d(&dados, &dev); - let cfg = CfgJepa { d_emb: 16, epocas: 40, lr: 5e-3, ..Default::default() }; - let rel = treinar_variante( - &patches, - &|n, r| mascara_intervalos_1d(n, 0.3, r), - &cfg, - VarianteJepa::TJepa, - None, - &dev, - ) - .unwrap(); - assert!( - rel.perda_final < rel.perda_inicial * 0.8, - "perda deveria cair: {} → {}", - rel.perda_inicial, - rel.perda_final - ); - assert!( - rel.std_emb_final > 0.05, - "sem colapso: std_final = {}", - rel.std_emb_final - ); - } - - #[test] - fn teste_adaptadores_formas() { - let dev = Device::Cpu; - let img = Tensor::randn(0f32, 1.0, (3, 16, 16), &dev).unwrap(); - let p = em_patches_2d(&img, 4, 4).unwrap(); - assert_eq!(p.dims().to_vec(), vec![16, 48]); - - let vid = Tensor::randn(0f32, 1.0, (4, 8, 8), &dev).unwrap(); - let t3 = em_tubos_3d(&vid, 2, 4, 4).unwrap(); - assert_eq!(t3.dims().to_vec(), vec![8, 32]); - - let spec = Tensor::randn(0f32, 1.0, (16, 32), &dev).unwrap(); - let ps = em_patches_espectrograma(&spec, 4, 8).unwrap(); - assert_eq!(ps.dims().to_vec(), vec![16, 32]); - - let ts = Tensor::randn(0f32, 1.0, (4, 32), &dev).unwrap(); - let pt_ = em_patches_serie(&ts, 8).unwrap(); - assert_eq!(pt_.dims().to_vec(), vec![16, 8]); - - let vol = Tensor::randn(0f32, 1.0, (8, 8, 8), &dev).unwrap(); - let vx = em_voxels(&vol, 4, 4, 4).unwrap(); - assert_eq!(vx.dims().to_vec(), vec![8, 64]); - - let pc = Tensor::randn(0f32, 1.0, (50, 3), &dev).unwrap(); - let mut rng = RngDeterministico::novo(5); - let gr = em_grupos_pontos(&pc, 6, 4, &mut rng).unwrap(); - assert_eq!(gr.dims().to_vec(), vec![6, 12]); - } - - #[test] - fn teste_sonic_moe_capacidade_e_balanceamento() { - // SM-1: carga por especialista ≤ capacidade; SM-2: aux ≥ ~1 quando - // roteador é ~uniforme (início). - let dev = Device::Cpu; - let moe = SonicMoe::novo(8, 4, 2, 6, &dev).unwrap(); - let x = Tensor::randn(0f32, 1.0, (20, 8), &dev).unwrap(); - let s = moe.fwd(&x).unwrap(); - for (e, c) in s.carga.iter().enumerate() { - assert!(*c <= 6, "especialista {e} com carga {c} > capacidade 6"); - } - assert_eq!(s.saida.dims().to_vec(), vec![20, 8]); - let aux = s.perda_aux.to_scalar::().unwrap() as f64; - // com roteador quase uniforme, aux ≈ E·Σ (1/E)·(1/E) = 1 - assert!(aux > 0.5 && aux < 3.0, "aux = {aux}"); - // cada token processado por ≤ n_top especialistas - let total_carga: usize = s.carga.iter().sum(); - assert!(total_carga <= 20 * 2, "carga total {total_carga} > n·topk"); - } - - #[test] - fn teste_replay_ssm_estabilidade_e_replay() { - // R-1: estados finitos e limitados; R-2: com replay α>0, após treinar - // a tarefa nova, a dinâmica antiga (magnitude do estado) permanece - // estável (não diverge). - let dev = Device::Cpu; - let mut ssm = ReplaySsm::novo(4, 6, 0.5, 8, &dev).unwrap(); - let vars = ssm.vars(); - let mut otim = crate::treino::AdamWManual::novo(vars, 1e-2, 0.0); - - // tarefa "antiga": sinal constante 1.0 - let x_antigo = Tensor::from_vec(vec![1.0f32; 16 * 4], (16, 4), &dev).unwrap(); - for _ in 0..30 { - let h = ssm.scan(&x_antigo).unwrap(); - let alvo = h.detach(); - ssm.passo_treino(&x_antigo, &alvo, &mut otim).unwrap(); - } - // estado após treino antigo: finito - let h1 = ssm.scan(&x_antigo).unwrap(); - let m1 = h1.abs().unwrap().max_all().unwrap().to_scalar::().unwrap(); - assert!(m1.is_finite() && m1 < 50.0, "estado explodiu: {m1}"); - - // tarefa "nova": sinal −1.0 — treino com replay - let x_novo = Tensor::from_vec(vec![-1.0f32; 16 * 4], (16, 4), &dev).unwrap(); - for _ in 0..30 { - let h = ssm.scan(&x_novo).unwrap(); - let alvo = h.detach(); - ssm.passo_treino(&x_novo, &alvo, &mut otim).unwrap(); - } - // a dinâmica antiga continua estável (sem explosão) - let h2 = ssm.scan(&x_antigo).unwrap(); - let m2 = h2.abs().unwrap().max_all().unwrap().to_scalar::().unwrap(); - assert!(m2.is_finite() && m2 < 50.0, "estado antigo explodiu: {m2}"); - // portas dentro de (0,1) - let a = ssm.a.as_tensor().to_vec1::().unwrap(); - for v in a { - assert!(v.is_finite()); - } - } -} diff --git a/src/jepa/mascaras.rs b/src/jepa/mascaras.rs new file mode 100644 index 0000000000000000000000000000000000000000..1bd9e00efe855ed5a8e372e438df05fb862fcf5e --- /dev/null +++ b/src/jepa/mascaras.rs @@ -0,0 +1,173 @@ +//! `jepa::mascaras` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +// --------------------------------------------------------------------------- +// Parte 2 — máscaras +// --------------------------------------------------------------------------- + +/// Máscara de blocos 2D (Image-JEPA): grade (h, w) de patches; sorteia +/// `n_blocos` retângulos aleatórios com fração alvo `frac` de patches. +pub fn mascara_blocos_2d(h: usize, w: usize, n_blocos: usize, frac: f64, rng: &mut RngDeterministico) -> Vec { + let total = h * w; + let alvo = (total as f64 * frac) as usize; + let mut masc = vec![false; total]; + let mut coberto = 0usize; + for _ in 0..n_blocos.max(1) { + if coberto >= alvo { + break; + } + let bh = 1 + rng.inteiro(h.min(4)); + let bw = 1 + rng.inteiro(w.min(4)); + let y0 = rng.inteiro(h + 1 - bh); + let x0 = rng.inteiro(w + 1 - bw); + for y in y0..y0 + bh { + for x in x0..x0 + bw { + let i = y * w + x; + if !masc[i] { + masc[i] = true; + coberto += 1; + } + } + } + } + masc +} + +/// Máscara de tubos 3D (V-JEPA 2): grade (t, h, w); tubos = retângulos que +/// atravessam o tempo. +pub fn mascara_tubos_3d(t: usize, h: usize, w: usize, n_tubos: usize, frac: f64, rng: &mut RngDeterministico) -> Vec { + let total = t * h * w; + let alvo = (total as f64 * frac) as usize; + let mut masc = vec![false; total]; + let mut coberto = 0usize; + for _ in 0..n_tubos.max(1) { + if coberto >= alvo { + break; + } + let bh = 1 + rng.inteiro(h.min(3)); + let bw = 1 + rng.inteiro(w.min(3)); + let y0 = rng.inteiro(h + 1 - bh); + let x0 = rng.inteiro(w + 1 - bw); + for tt in 0..t { + for y in y0..y0 + bh { + for x in x0..x0 + bw { + let i = (tt * h + y) * w + x; + if !masc[i] { + masc[i] = true; + coberto += 1; + } + } + } + } + } + masc +} + +/// Máscara de bandas (Audio-JEPA): grade (f, tau) — bandas de frequência +/// horizontais com extensão temporal. +pub fn mascara_bandas(f: usize, tau: usize, n_bandas: usize, frac: f64, rng: &mut RngDeterministico) -> Vec { + mascara_blocos_2d(f, tau, n_bandas, frac, rng) // blocos na grade (f, tau) +} + +/// Máscara de blocos 3D (3D-JEPA): grade (x, y, z) de voxels. +pub fn mascara_blocos_3d(x: usize, y: usize, z: usize, n_blocos: usize, frac: f64, rng: &mut RngDeterministico) -> Vec { + mascara_tubos_3d(x, y, z, n_blocos, frac, rng) +} + +/// Máscara de segmentos canal×tempo (Brain-JEPA): canais inteiros OU +/// janelas temporais (aleatório por chamada). +pub fn mascara_segmentos(c: usize, t: usize, frac: f64, rng: &mut RngDeterministico) -> Vec { + let total = c * t; + let alvo = (total as f64 * frac) as usize; + let mut masc = vec![false; total]; + if rng.inteiro(2) == 0 { + // canais inteiros + let mut coberto = 0; + let mut canais: Vec = (0..c).collect(); + // embaralhar + for i in (1..canais.len()).rev() { + let j = rng.inteiro(i + 1); + canais.swap(i, j); + } + for &ch in canais.iter() { + if coberto >= alvo { + break; + } + for tt in 0..t { + masc[ch * t + tt] = true; + coberto += 1; + } + } + } else { + // janelas temporais + let mut coberto = 0; + let mut t0 = 0; + while coberto < alvo && t0 < t { + let len = 1 + rng.inteiro((t / 4).max(1)); + for ch in 0..c { + for tt in t0..(t0 + len).min(t) { + masc[ch * t + tt] = true; + coberto += 1; + } + } + t0 += len + 1; + } + } + masc +} + +/// Máscara 1D (T-JEPA): intervalos contíguos em n posições. +pub fn mascara_intervalos_1d(n: usize, frac: f64, rng: &mut RngDeterministico) -> Vec { + let alvo = (n as f64 * frac) as usize; + let mut masc = vec![false; n]; + let mut coberto = 0; + while coberto < alvo { + let len = 1 + rng.inteiro((n / 8).max(1)); + let i0 = rng.inteiro(n); + for i in i0..(i0 + len).min(n) { + if !masc[i] { + masc[i] = true; + coberto += 1; + } + } + } + masc +} + +/// Amostragem dos pontos mais distantes (FPS) para Point-JEPA. +pub fn pontos_mais_distantes(pontos: &[Vec], k: usize, rng: &mut RngDeterministico) -> Vec { + let n = pontos.len(); + if n == 0 || k == 0 { + return vec![]; + } + let k = k.min(n); + let mut centros = vec![rng.inteiro(n)]; + let mut dist_min: Vec = pontos + .iter() + .map(|p| distancia_eucl(p, &pontos[centros[0]])) + .collect(); + while centros.len() < k { + let prox = dist_min + .iter() + .enumerate() + .filter(|(i, _)| !centros.contains(i)) + .max_by(|a, b| a.1.partial_cmp(b.1).unwrap()) + .map(|(i, _)| i) + .unwrap_or(0); + centros.push(prox); + for (i, p) in pontos.iter().enumerate() { + let d = distancia_eucl(p, &pontos[prox]); + if d < dist_min[i] { + dist_min[i] = d; + } + } + } + centros +} + +pub(crate) fn distancia_eucl(a: &[f64], b: &[f64]) -> f64 { + a.iter().zip(b).map(|(x, y)| (x - y) * (x - y)).sum::().sqrt() +} diff --git a/src/jepa/mod.rs b/src/jepa/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..e68e71515d2b10af3e0c2d7d6a47a7e3a9fb8dd7 --- /dev/null +++ b/src/jepa/mod.rs @@ -0,0 +1,61 @@ +//! Suíte JEPA — módulos: núcleo, máscaras, patches, variantes, +//! SonicMoE, ReplaySSM (divisão SRP v0.10.0 — r-35 §2). + +//! Suíte JEPA v0.5.0 — núcleo unificado + 12 variantes + SonicMoE + ReplaySSM. +//! +//! **Núcleo (todas as variantes compartilham):** +//! - codificador `E_θ` (MLp SiLU), preditor `P_φ` (condicionado por posição/ +//! ação/edição), codificador-alvo EMA `E_ξ` com `ξ ← m·ξ + (1−m)·θ` +//! (gradiente cortado — alvo nunca recebe gradiente); +//! - regularizador variância-covariância (VICReg-lite) anti-colapso: +//! `V(z) = Σ_j max(0, γ − std(z_j))²` + fora-diagonal da covariância. +//! - perda: `L = Σ_b ‖P(z_ctx, pos_b) − sg(ẑ_b)‖²/B + λ_vc·V`. +//! +//! **Prova J-1 (impossibilidade de colapso, em r-20):** se todas as +//! incorporações colapsam para constante, `std(z_j) = 0 ∀j` ⇒ `V = d·γ² > 0`; +//! logo o colapso total NÃO é minimizador com `λ_vc > 0`. +//! +//! **Prova J-2 (EMA = stop-grad + ponto fixo):** `∂L/∂ξ = 0` (detach); se +//! `θ_t → θ*` então `ξ_t → θ*` (recursão linear contratante com `m < 1`). +//! +//! **Prova J-3 (decomposição de informação):** com `P` suficientemente +//! rico, `min_P L = Σ_b Var(ẑ_b | z_ctx)` — a perda retém só o componentes +//! previsíveis do alvo dado o contexto. +//! +//! **Variantes** (esquema de máscara + adaptador de modalidade): +//! Image-JEPA (blocos 2D) · V-JEPA 2 (tubos 3D + cond. de ação) · MC-JEPA +//! (cruz-modal, dois codificadores) · Audio-JEPA (bandas de espectrograma) · +//! Point-JEPA (grupos FPS) · 3D-JEPA (voxels) · Brain-JEPA (canal×tempo) · +//! T-JEPA (série temporal canal-independente) · LeJEPA (edição-condicionado) · +//! BJEPA (bidirecional com consistência de ciclo). +//! +//! **SonicMoE** — MoE com roteamento top-k, capacidade por especialista +//! (tile-aware) e perda auxiliar de balanceamento (argumento Switch: +//! `L_aux = E·Σ f_e·p_e ≥ 1` com igualdade sse uniforme). +//! +//! **ReplaySSM** — SSM de porta diagonal (`h_t = σ(a)⊙h_{t−1} + +//! (1−σ(a))⊙(Bx_t)`, estabilidade herdada do Teo S-2') + replay de experiências +//! para aprendizado continuado (interpolação de gradientes limita o +//! esquecimento — Teo R-1). + +use candle_core::{Device, Result, Tensor, Var}; +use crate::camadas::Linear; +use crate::rng::RngDeterministico; + +// ---- divisão SRP v0.10.0 (r-35 §2): submódulos + re-exports ---- +#[cfg(test)] +mod testes; +mod mascaras; +mod nucleo; +mod patches; +mod replay_ssm; +mod sonic_moe; +mod variantes; + +pub use mascaras::*; +pub use nucleo::*; +pub use patches::*; +pub use replay_ssm::*; +pub use sonic_moe::*; +pub use variantes::*; + diff --git a/src/jepa/nucleo.rs b/src/jepa/nucleo.rs new file mode 100644 index 0000000000000000000000000000000000000000..6f481d3bd1336632adcc037e7b78ada79524bfc9 --- /dev/null +++ b/src/jepa/nucleo.rs @@ -0,0 +1,211 @@ +//! `jepa::nucleo` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +// --------------------------------------------------------------------------- +// Parte 1 — núcleo +// --------------------------------------------------------------------------- + +/// MLP 2 camadas com SiLU (d → h → d_saida). +pub struct MlpJepa { + pub l1: Linear, + pub l2: Linear, + pub d_entrada: usize, + pub d_saida: usize, +} + +impl MlpJepa { + pub fn novo(d_entrada: usize, d_oculta: usize, d_saida: usize, dev: &Device) -> Result { + let s1 = (1.0 / (d_entrada as f64).sqrt()) * 0.9; + let s2 = (1.0 / (d_oculta as f64).sqrt()) * 0.9; + Ok(Self { + l1: Linear::sem_vies(d_entrada, d_oculta, s1, dev)?, + l2: Linear::sem_vies(d_oculta, d_saida, s2, dev)?, + d_entrada, + d_saida, + }) + } + + pub fn fwd(&self, x: &Tensor) -> Result { + let h = candle_nn::ops::silu(&self.l1.fwd(x)?)?; + self.l2.fwd(&h) + } + + pub fn vars(&self) -> Vec { + [self.l1.vars(), self.l2.vars()].concat() + } + + /// EMA: `self ← m·self + (1−m)·outro` (usado no codificador-alvo). + pub fn ema_de(&mut self, outro: &MlpJepa, m: f64) -> Result<()> { + for (a, b) in self.vars().iter().zip(outro.vars().iter()) { + let novo = (a.as_tensor().affine(m, 0.0)? + b.as_tensor().affine(1.0 - m, 0.0)?)?; + a.set(&novo)?; + } + Ok(()) + } +} + +/// Núcleo JEPA: codificador + preditor + alvo EMA + regularizador VC. +pub struct NucleoJepa { + pub encoder: MlpJepa, + pub preditor: MlpJepa, + pub alvo: MlpJepa, + /// tabela de posições (blocos) — entrada extra do preditor. + pub pos: Var, + pub d_emb: usize, + pub d_cond: usize, + pub m: f64, + pub lambda_vc: f64, + pub gamma_vc: f64, +} + +impl NucleoJepa { + pub fn novo( + d_patch: usize, + d_emb: usize, + d_cond: usize, + n_pos: usize, + m: f64, + lambda_vc: f64, + gamma_vc: f64, + dev: &Device, + ) -> Result { + let encoder = MlpJepa::novo(d_patch, 2 * d_emb, d_emb, dev)?; + let preditor = MlpJepa::novo(d_emb + d_cond + d_emb, 2 * d_emb, d_emb, dev)?; + // alvo nasce IGUAL ao encoder (ponto de partida do EMA) + let mut alvo = MlpJepa::novo(d_patch, 2 * d_emb, d_emb, dev)?; + for (va, vb) in alvo.vars().iter().zip(encoder.vars().iter()) { + va.set(vb.as_tensor())?; + } + let pos_t = Tensor::randn(0f32, 0.02, (n_pos.max(1), d_emb), dev)?; + Ok(Self { + encoder, + preditor, + alvo, + pos: Var::from_tensor(&pos_t)?, + d_emb, + d_cond, + m, + lambda_vc, + gamma_vc, + }) + } + + pub fn vars(&self) -> Vec { + let mut v = self.encoder.vars(); + v.extend(self.preditor.vars()); + v.push(self.pos.clone()); + v // NOTA: alvo NÃO entra — é buffer EMA + } + + pub fn ema_passo(&mut self) -> Result<()> { + self.alvo.ema_de(&self.encoder, self.m) + } + + /// Codifica um conjunto de patches (n, d_patch) → (n, d_emb). + pub fn codificar(&self, patches: &Tensor) -> Result { + self.encoder.fwd(patches) + } + + /// Codifica com o alvo EMA — SEM gradiente (detach por construção: as + /// vars do alvo não estão no otimizador e o tensor é desconectado). + pub fn alvo_codificar(&self, patches: &Tensor) -> Result { + Ok(self.alvo.fwd(patches)?.detach()) + } + + /// Prediz o embedding do bloco `i_pos` a partir do contexto `z_ctx` + /// (d_emb) e da condição `cond` (d_cond; zeros se None). + pub fn predizer(&self, z_ctx: &Tensor, i_pos: usize, cond: Option<&Tensor>) -> Result { + // z_ctx: (b, d_emb) ou (d_emb,) + let b = z_ctx.dim(0).unwrap_or(1); + let dev = z_ctx.device(); + let pos = self.pos.as_tensor().get(i_pos.min(self.pos.dim(0)?.saturating_sub(1)))?; + let pos_b = pos.broadcast_as((b, self.d_emb))?; + let cond_b = match cond { + Some(c) => { + if c.dims().len() == 1 && b > 1 { + c.broadcast_as((b, self.d_cond))? + } else { + c.clone() + } + } + None => Tensor::zeros((b, self.d_cond), candle_core::DType::F32, dev)?, + }; + let entrada = Tensor::cat(&[z_ctx, &pos_b, &cond_b], candle_core::D::Minus1)?; + self.preditor.fwd(&entrada) + } + + /// Regularizador variância-covariância do lote de incorporações z (n, d). + /// + /// **J-1:** no colapso total `z_i = c ∀i` ⇒ `std_j = 0` ⇒ `V = d·γ²`. + pub fn perda_vc(&self, z: &Tensor) -> Result { + let n = z.dim(0)? as f64; + let d = self.d_emb; + let media = z.mean_keepdim(0)?; // (1, d) + let centr = z.broadcast_sub(&media)?; + let var = (centr.sqr()?.sum(0)? / n)?; // (d,) + let std = var.sqrt()?; + // dobradiça: max(0, γ − std)² + let gamma_t = + Tensor::new(self.gamma_vc as f32, z.device())?.broadcast_as(std.dims())?; + let gap = gamma_t.broadcast_sub(&std)?.clamp(0.0, f64::INFINITY)?; + let v = gap.sqr()?.sum_all()?; + // covariância fora-diagonal: C = centrᵀ·centr/n; perda = ‖C − diag‖²_F/(2d) + let cov = (centr.transpose(0, 1)?.matmul(¢r)? / n)?; // (d, d) + let mask_diag = { + let mut m = vec![0f32; d * d]; + for i in 0..d { + m[i * d + i] = 1.0; + } + Tensor::from_vec(m, (d, d), z.device())? + }; + let fora = ((&cov * &mask_diag)?.sqr()?.sum_all()? / (2.0 * d.max(1) as f64))?; + let v_norm = (v / d.max(1) as f64)?; + Ok((v_norm + fora)?) + } + + /// Perda JEPA padrão para UM lote: contexto (número variável de patches), + /// lista de blocos-alvo (posições), condição opcional. + /// + /// `z_ctx` = incorporação agregada do contexto (b, d_emb); + /// `alvos`: para cada elemento do lote, Vec<(i_pos, patches_alvo)>. + pub fn perda_lote( + &self, + z_ctx: &Tensor, + alvos: &[Vec<(usize, Tensor)>], + cond: Option<&Tensor>, + z_ctx_todos: &Tensor, + ) -> Result<(Tensor, f64, f64)> { + let b = z_ctx.dim(0)?; + let dev = z_ctx.device(); + let mut perdas: Vec = Vec::new(); + let mut n_alvos = 0usize; + for (i, blocos) in alvos.iter().enumerate() { + for (i_pos, patches_alvo) in blocos { + // ẑ_b = média das incorporações-alvo dos patches do bloco + let zt = self.alvo_codificar(patches_alvo)?; // (m, d_emb) + let zt_m = zt.mean(0)?; // (d_emb,) + let zt_b = zt_m.broadcast_as((1, self.d_emb))?; + let zc = z_ctx.get(i)?.broadcast_as((1, self.d_emb))?; + let c_i = cond.map(|c| c.get(i)?.broadcast_as((1, self.d_cond))).transpose()?; + let pred = self.predizer(&zc, *i_pos, c_i.as_ref())?; + let dif = (&pred - &zt_b)?; + let l = (dif.sqr()?.sum_all()? / self.d_emb as f64)?; + perdas.push(l); + n_alvos += 1; + } + } + let l_pred = if perdas.is_empty() { + Tensor::zeros((), candle_core::DType::F32, &dev)? + } else { + (Tensor::stack(&perdas, 0)?.sum_all()? / n_alvos.max(1) as f64)? + }; + let l_vc = self.perda_vc(z_ctx_todos)?; + let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(self.lambda_vc, 0.0)?)?; + let lp = l_pred.to_scalar::()? as f64; + let lv = l_vc.to_scalar::()? as f64; + Ok((total, lp, lv)) + } +} diff --git a/src/jepa/patches.rs b/src/jepa/patches.rs new file mode 100644 index 0000000000000000000000000000000000000000..88daf2ca5642a866ed102a0a20d9ba4cac61f243 --- /dev/null +++ b/src/jepa/patches.rs @@ -0,0 +1,150 @@ +//! `jepa::patches` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +// --------------------------------------------------------------------------- +// Parte 3 — adaptadores de modalidade +// --------------------------------------------------------------------------- + +/// (C, H, W) → patches (H/ph · W/pw, C·ph·pw) — Image-JEPA. +pub fn em_patches_2d(img: &Tensor, ph: usize, pw: usize) -> Result { + let (c, h, w) = (img.dim(0)?, img.dim(1)?, img.dim(2)?); + let dev = img.device(); + let mut linhas: Vec = Vec::new(); + for y in (0..h).step_by(ph) { + for x in (0..w).step_by(pw) { + let hh = ph.min(h - y); + let ww = pw.min(w - x); + let patch = img.narrow(0, 0, c)?.narrow(1, y, hh)?.narrow(2, x, ww)?; + // padding para (C, ph, pw) se borda + let patch = if hh < ph || ww < pw { + let mut buf = patch.flatten_all()?.to_vec1::()?; + let falta = c * ph * pw - buf.len(); + buf.extend(std::iter::repeat(0f32).take(falta)); + Tensor::from_vec(buf, (c * ph * pw,), &dev)? + } else { + patch.flatten_all()? + }; + linhas.push(patch); + } + } + Ok(Tensor::stack(&linhas, 0)?) +} + +/// (T, H, W) → tubos (T/pt · H/ph · W/pw, pt·ph·pw) — V-JEPA 2. +pub fn em_tubos_3d(vid: &Tensor, pt: usize, ph: usize, pw: usize) -> Result { + let (t, h, w) = (vid.dim(0)?, vid.dim(1)?, vid.dim(2)?); + let dev = vid.device(); + let mut linhas: Vec = Vec::new(); + for tt in (0..t).step_by(pt) { + let tl = pt.min(t - tt); + for y in (0..h).step_by(ph) { + for x in (0..w).step_by(pw) { + let hh = ph.min(h - y); + let ww = pw.min(w - x); + let tubo = vid + .narrow(0, tt, tl)? + .narrow(1, y, hh)? + .narrow(2, x, ww)? + .flatten_all()?; + let mut buf = tubo.to_vec1::()?; + let falta = pt * ph * pw - buf.len(); + buf.extend(std::iter::repeat(0f32).take(falta)); + linhas.push(Tensor::from_vec(buf, (pt * ph * pw,), &dev)?); + } + } + } + Ok(Tensor::stack(&linhas, 0)?) +} + +/// (F, τ) → patches (F/pf · τ/pt, pf·pt) — Audio-JEPA. +pub fn em_patches_espectrograma(spec: &Tensor, pf: usize, pt: usize) -> Result { + let (f, tau) = (spec.dim(0)?, spec.dim(1)?); + let dev = spec.device(); + let mut linhas: Vec = Vec::new(); + for i in (0..f).step_by(pf) { + for j in (0..tau).step_by(pt) { + let ff = pf.min(f - i); + let tt = pt.min(tau - j); + let p = spec.narrow(0, i, ff)?.narrow(1, j, tt)?.flatten_all()?; + let mut buf = p.to_vec1::()?; + let falta = pf * pt - buf.len(); + buf.extend(std::iter::repeat(0f32).take(falta)); + linhas.push(Tensor::from_vec(buf, (pf * pt,), &dev)?); + } + } + Ok(Tensor::stack(&linhas, 0)?) +} + +/// (C, T) → patches canal-independentes (C · T/pt, pt) — T-JEPA/Brain-JEPA. +pub fn em_patches_serie(ts: &Tensor, pt: usize) -> Result { + let (c, t) = (ts.dim(0)?, ts.dim(1)?); + let dev = ts.device(); + let mut linhas: Vec = Vec::new(); + for ch in 0..c { + for j in (0..t).step_by(pt) { + let tt = pt.min(t - j); + let p = ts.narrow(0, ch, 1)?.narrow(1, j, tt)?.flatten_all()?; + let mut buf = p.to_vec1::()?; + let falta = pt - buf.len(); + buf.extend(std::iter::repeat(0f32).take(falta)); + linhas.push(Tensor::from_vec(buf, (pt,), &dev)?); + } + } + Ok(Tensor::stack(&linhas, 0)?) +} + +/// (X, Y, Z) → voxels (X/px · Y/py · Z/pz, px·py·pz) — 3D-JEPA. +pub fn em_voxels(vol: &Tensor, px: usize, py: usize, pz: usize) -> Result { + let (x, y, z) = (vol.dim(0)?, vol.dim(1)?, vol.dim(2)?); + let dev = vol.device(); + let mut linhas: Vec = Vec::new(); + for i in (0..x).step_by(px) { + for j in (0..y).step_by(py) { + for k in (0..z).step_by(pz) { + let lx = px.min(x - i); + let ly = py.min(y - j); + let lz = pz.min(z - k); + let p = vol.narrow(0, i, lx)?.narrow(1, j, ly)?.narrow(2, k, lz)?.flatten_all()?; + let mut buf = p.to_vec1::()?; + let falta = px * py * pz - buf.len(); + buf.extend(std::iter::repeat(0f32).take(falta)); + linhas.push(Tensor::from_vec(buf, (px * py * pz,), &dev)?); + } + } + } + Ok(Tensor::stack(&linhas, 0)?) +} + +/// Nuvem de pontos (n, d) → grupos FPS (k, m·d): k centros por FPS, cada +/// grupo = os m vizinhos mais próximos do centro (concatenados) — Point-JEPA. +pub fn em_grupos_pontos(pc: &Tensor, k: usize, m: usize, rng: &mut RngDeterministico) -> Result { + let n = pc.dim(0)?; + let d = pc.dim(1)?; + let coords = pc.to_vec2::()?; + let coords_f: Vec> = coords.iter().map(|r| r.iter().map(|&v| v as f64).collect()).collect(); + let centros = pontos_mais_distantes(&coords_f, k, rng); + let dev = pc.device(); + let mut linhas: Vec = Vec::new(); + for &ci in ¢ros { + // m vizinhos mais próximos do centro ci (incluindo ele próprio) + let mut ord: Vec = (0..n).collect(); + ord.sort_by(|&a, &b| { + let da = distancia_eucl(&coords_f[a], &coords_f[ci]); + let db = distancia_eucl(&coords_f[b], &coords_f[ci]); + da.partial_cmp(&db).unwrap() + }); + let mut buf: Vec = Vec::with_capacity(m * d); + for &idx in ord.iter().take(m) { + for j in 0..d { + buf.push(coords[idx][j]); + } + } + let falta = m * d - buf.len(); + buf.extend(std::iter::repeat(0f32).take(falta)); + linhas.push(Tensor::from_vec(buf, (m * d,), &dev)?); + } + Ok(Tensor::stack(&linhas, 0)?) +} diff --git a/src/jepa/replay_ssm.rs b/src/jepa/replay_ssm.rs new file mode 100644 index 0000000000000000000000000000000000000000..324dbbd246af49e29c05f922ed567d9e82616272 --- /dev/null +++ b/src/jepa/replay_ssm.rs @@ -0,0 +1,107 @@ +//! `jepa::replay_ssm` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +// --------------------------------------------------------------------------- +// Parte 6 — ReplaySSM +// --------------------------------------------------------------------------- + +/// SSM de porta diagonal com replay para aprendizado continuado. +/// +/// Recorrência: `h_t = σ(a) ⊙ h_{t−1} + (1 − σ(a)) ⊙ (B x_t)`. +/// +/// **Prova R-1 (estabilidade):** `|σ(a)| < 1` ⇒ perturbações de `h_0` +/// decaem geometricamente (mesma estrutura do Teo S-2' do SSD-Δ). +/// +/// **Prova R-2 (limite de esquecimento):** o passo de treino mistura +/// gradientes `(1−α)·∇L_novo + α·∇L_replay`; para `L_antiga` μ-fortemente +/// convexa com gradientes novos limitados por `G`, o afastamento do ótimo +/// antigo por passo é `≤ (1−α)·η·G` (sem replay: `≤ η·G`) — o replay +/// contrai o drift por um fator `1/(1−α)` no regime linear. +pub struct ReplaySsm { + pub a: Var, // (d,) — porta (sigmoid no uso) + pub b: Var, // (d_in, d) — projeção de entrada + pub d: usize, + pub alpha_replay: f64, + pub buffer: Vec, + pub capacidade_buffer: usize, +} + +impl ReplaySsm { + pub fn novo(d_in: usize, d: usize, alpha_replay: f64, capacidade_buffer: usize, dev: &Device) -> Result { + let a_t = Tensor::randn(0f32, 0.5, (d,), dev)?; + let b_t = Tensor::randn(0f32, 0.1, (d_in, d), dev)?; + Ok(Self { + a: Var::from_tensor(&a_t)?, + b: Var::from_tensor(&b_t)?, + d, + alpha_replay, + buffer: Vec::new(), + capacidade_buffer, + }) + } + + pub fn vars(&self) -> Vec { + vec![self.a.clone(), self.b.clone()] + } + + /// Scan: x (T, d_in) → h (T, d). Diferenciável (candle autograd). + pub fn scan(&self, x: &Tensor) -> Result { + let t = x.dim(0)?; + let dev = x.device(); + let porta = candle_nn::ops::sigmoid(&self.a.as_tensor().broadcast_as((1, self.d))?)?; // (1, d) + let um_menos = porta.affine(-1.0, 1.0)?; // 1 − σ(a) + let entrada = x.matmul(&self.b.as_tensor())?; // (T, d) + let mut h = Tensor::zeros((1, self.d), candle_core::DType::F32, &dev)?; + let mut saidas: Vec = Vec::with_capacity(t); + for i in 0..t { + let xt = entrada.narrow(0, i, 1)?; + h = ((&porta * &h)? + (&um_menos * &xt)?)?; + saidas.push(h.clone()); + } + Ok(Tensor::stack(&saidas, 0)?) + } + + /// Empurra uma janela de treino no buffer (FIFO). + pub fn guardar(&mut self, x: &Tensor) { + if self.buffer.len() >= self.capacidade_buffer { + self.buffer.remove(0); + } + self.buffer.push(x.detach()); + } + + /// Um passo de treino no lote novo (com mistura de replay). + /// Devolve (perda_total, perda_novo, perda_replay). + pub fn passo_treino( + &mut self, + x: &Tensor, + alvo: &Tensor, + otimizador: &mut crate::treino::AdamWManual, + ) -> Result<(f64, f64, f64)> { + // perda nova + let h = self.scan(x)?; + let dif = (&h - alvo)?; + let perda_novo = dif.sqr()?.mean_all()?; + // replay + let mut perda_total = perda_novo.clone(); + let mut p_r = 0.0; + if !self.buffer.is_empty() { + let idx = self.buffer.len() - 1; + let antigo = self.buffer[idx].clone(); + let hr = self.scan(&antigo)?; + // alvo do replay: manter a dinâmica aprendida estável (magnitudes + // finitas) — auto-supervisão de estabilidade + let dr = hr.sqr()?.mean_all()?; + perda_total = (perda_total.affine(1.0 - self.alpha_replay, 0.0)? + + dr.affine(self.alpha_replay, 0.0)?)?; + p_r = dr.to_scalar::()? as f64; + } + let pn = perda_novo.to_scalar::()? as f64; + otimizador.passo(crate::treino::grads_achatados(&perda_total, &self.vars())?); + self.guardar(x); + let pt = perda_total.to_scalar::()? as f64; + Ok((pt, pn, p_r)) + } +} diff --git a/src/jepa/sonic_moe.rs b/src/jepa/sonic_moe.rs new file mode 100644 index 0000000000000000000000000000000000000000..eb9d3e2e90bdac1e23e4a34dfd5ba2ceca80b6fa --- /dev/null +++ b/src/jepa/sonic_moe.rs @@ -0,0 +1,155 @@ +//! `jepa::sonic_moe` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +// --------------------------------------------------------------------------- +// Parte 5 — SonicMoE (IO/tile-aware) +// --------------------------------------------------------------------------- + +/// MoE top-k com capacidade por especialista e perda de balanceamento. +/// +/// **Prova SM-1 (limite de capacidade):** o roteamento tile-aware mantém no +/// máximo `C` tokens por especialista por ladrilho — o drop é determinístico +/// (ordem de chegada) e o nº de tokens descartados é `Σ_e max(0, load_e − C)`. +/// +/// **Prova SM-2 (balanceamento):** `L_aux = E·Σ_e f_e·p_e`; com roteamento +/// auto-consistente (`f = p`), `L_aux = E·Σ p_e² ≥ 1` com igualdade sse +/// `p_e = 1/E` (Cauchy–Schchwarz) — o mínimo força o roteador uniforme. +pub struct SonicMoe { + pub roteador: Linear, // d → E + pub especialistas: Vec, // E experts d → d + pub n_top: usize, + pub capacidade: usize, + pub lambda_aux: f64, +} + +pub struct SaidaMoe { + pub saida: Tensor, + pub perda_aux: Tensor, + pub carga: Vec, // tokens efetivamente processados por especialista + pub descartados: usize, // tokens com especialistas cortados pela capacidade +} + +impl SonicMoe { + pub fn novo(d: usize, n_expertos: usize, n_top: usize, capacidade: usize, dev: &Device) -> Result { + let roteador = Linear::sem_vies(d, n_expertos, 0.1, dev)?; + let especialistas = (0..n_expertos) + .map(|_| MlpJepa::novo(d, 2 * d, d, dev)) + .collect::>>()?; + Ok(Self { roteador, especialistas, n_top, capacidade, lambda_aux: 0.01 }) + } + + pub fn vars(&self) -> Vec { + let mut v = self.roteador.vars(); + for e in &self.especialistas { + v.extend(e.vars()); + } + v + } + + /// Forward tile-aware: (n, d) → (n, d). + pub fn fwd(&self, x: &Tensor) -> Result { + let n = x.dim(0)?; + let d = x.dim(1)?; + let e = self.especialistas.len(); + let dev = x.device(); + let logits = self.roteador.fwd(x)?; // (n, E) + let probs = candle_nn::ops::softmax(&logits, candle_core::D::Minus1)?; + + // top-k por token (não diferenciável — pesos do top-k são dif. via probs) + let probs_v = probs.to_vec2::()?; + let mut carga = vec![0usize; e]; + let mut descartados = 0usize; + // acumulador de saída (n, d) — construído por soma de contribuições + let mut saida = Tensor::zeros((n, d), candle_core::DType::F32, &dev)?; + let mut peso_total = Tensor::zeros((n, 1), candle_core::DType::F32, &dev)?; + // por especialista (tile): tokens cujo top-k contém e e com carga < C + for ei in 0..e { + // candidatos: tokens ordenados por prob do especialista ei + let mut cand: Vec<(usize, f32)> = (0..n) + .map(|i| (i, probs_v[i][ei])) + .collect(); + cand.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + let mut escolhidos: Vec = Vec::new(); + for (i, _) in cand { + if carga[ei] >= self.capacidade { + break; + } + // verifica se ei está no top-k do token i + let mut ordem: Vec<(f32, usize)> = + (0..e).map(|k| (probs_v[i][k], k)).collect(); + ordem.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap()); + let top: Vec = ordem.iter().rev().take(self.n_top).map(|(_, k)| *k).collect(); + if top.contains(&ei) { + escolhidos.push(i); + carga[ei] += 1; + } + } + if escolhidos.is_empty() { + continue; + } + // tile do especialista: uma única passagem em lote + let idx: Vec = escolhidos.iter().map(|&i| i as u32).collect(); + let n_e = idx.len(); + let idx_t = Tensor::from_vec(idx, (n_e,), &dev)?; + let tile = x.index_select(&idx_t, 0)?; + let y = self.especialistas[ei].fwd(&tile)?; // (m, d) + // pesos: prob do especialista ei para cada token escolhido + let w: Vec = escolhidos.iter().map(|&i| probs_v[i][ei]).collect(); + let n_w = escolhidos.len(); + let w_t = Tensor::from_vec(w.clone(), (n_w, 1), &dev)?; + let contrib = y.broadcast_mul(&w_t)?; + // scatter-add: acumula nas posições + let mut buf = saida.to_vec2::()?; + let contrib_v = contrib.to_vec2::()?; + for (k, &i) in escolhidos.iter().enumerate() { + for j in 0..d { + buf[i][j] += contrib_v[k][j]; + } + } + saida = Tensor::from_vec( + buf.into_iter().flatten().collect::>(), + (n, d), + &dev, + )?; + let mut pt = peso_total.to_vec2::()?; + for (k, &i) in escolhidos.iter().enumerate() { + pt[i][0] += w[k]; + } + peso_total = Tensor::from_vec( + pt.into_iter().flatten().collect::>(), + (n, 1), + &dev, + )?; + } + // tokens sem especialista (todos cortados) → descartados; saída 0 + let pt_v = peso_total.to_vec2::()?; + for i in 0..n { + if pt_v[i][0] == 0.0 { + descartados += 1; + } + } + // renormaliza pelo peso efetivo (evita viés da capacidade) + let denom = peso_total.clamp(1e-6, f64::INFINITY)?; + let saida = saida.broadcast_div(&denom)?; + + // perda auxiliar: E·Σ f_e·p_e (f = fração de tokens cujo top-1 é e) + let mut f = vec![0f32; e]; + for i in 0..n { + let (mut melhor, mut mp) = (0usize, f32::NEG_INFINITY); + for k in 0..e { + if probs_v[i][k] > mp { + mp = probs_v[i][k]; + melhor = k; + } + } + f[melhor] += 1.0 / n as f32; + } + let f_t = Tensor::from_vec(f, (e,), &dev)?; + let p_media = probs.mean(0)?; // (E,) + let perda_aux = ((&f_t * &p_media)?.sum_all()? * (e as f64))?; + Ok(SaidaMoe { saida, perda_aux, carga, descartados }) + } +} diff --git a/src/jepa/testes.rs b/src/jepa/testes.rs new file mode 100644 index 0000000000000000000000000000000000000000..812ba8b775611be7452fe2caf10ad5da14400b18 --- /dev/null +++ b/src/jepa/testes.rs @@ -0,0 +1,212 @@ +//! `jepa::testes` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +#[cfg(test)] +mod testes { + use super::*; + + fn tensor_2d(dados: &[Vec], dev: &Device) -> Tensor { + let flat: Vec = dados.iter().flat_map(|v| v.iter().map(|&x| x as f32)).collect(); + Tensor::from_vec(flat, (dados.len(), dados[0].len()), dev).unwrap() + } + + #[test] + fn teste_mascaras_validas() { + // fração respeitada ±, particionamento contexto/alvo não-vazio + let mut rng = RngDeterministico::novo(1); + let m2 = mascara_blocos_2d(8, 8, 4, 0.3, &mut rng); + assert_eq!(m2.len(), 64); + let frac = m2.iter().filter(|&&b| b).count() as f64 / 64.0; + assert!(frac >= 0.15 && frac <= 0.55, "frac 2d = {frac}"); + assert!(m2.iter().any(|&b| !b), "contexto vazio"); + + let m3 = mascara_tubos_3d(4, 6, 6, 3, 0.3, &mut rng); + assert_eq!(m3.len(), 144); + let f3 = m3.iter().filter(|&&b| b).count() as f64 / 144.0; + assert!(f3 >= 0.15 && f3 <= 0.6, "frac 3d = {f3}"); + + let ms = mascara_segmentos(4, 32, 0.25, &mut rng); + assert_eq!(ms.len(), 128); + assert!(ms.iter().any(|&b| b) && ms.iter().any(|&b| !b)); + + let m1 = mascara_intervalos_1d(64, 0.3, &mut rng); + let f1 = m1.iter().filter(|&&b| b).count() as f64 / 64.0; + assert!(f1 >= 0.25 && f1 <= 0.45, "frac 1d = {f1}"); + } + + #[test] + fn teste_fps_cobre() { + let mut rng = RngDeterministico::novo(2); + let pontos: Vec> = (0..40) + .map(|i| { + let a = i as f64 * 0.1; + vec![a.cos() * 3.0, a.sin() * 3.0, (i % 7) as f64 * 0.3] + }) + .collect(); + let centros = pontos_mais_distantes(&pontos, 6, &mut rng); + assert_eq!(centros.len(), 6); + // centros distintos + let mut s = centros.clone(); + s.sort(); + s.dedup(); + assert_eq!(s.len(), 6); + } + + #[test] + fn teste_j1_colapso_impossivel() { + // No colapso total (z constante), a perda VC = γ² > 0 — verificando a + // fórmula implementada com um lote constante. + let dev = Device::Cpu; + let nucleo = NucleoJepa::novo(4, 8, 0, 4, 0.99, 1.0, 1.0, &dev).unwrap(); + let z = Tensor::from_vec(vec![0.5f32; 8 * 8], (8, 8), &dev).unwrap(); + let lvc = nucleo.perda_vc(&z).unwrap(); + let v = lvc.to_scalar::().unwrap() as f64; + // std=0 ⇒ dobradiça = γ = 1 ⇒ V/d = 1; cov = 0 + assert!(v > 0.9, "perda VC no colapso = {v} (deveria ser ~1)"); + } + + #[test] + fn teste_j2_ema_ponto_fixo() { + // encoder congelado ⇒ após muitas passadas EMA, alvo = encoder + let dev = Device::Cpu; + let mut nucleo = NucleoJepa::novo(4, 8, 0, 4, 0.5, 0.1, 1.0, &dev).unwrap(); + for _ in 0..40 { + nucleo.ema_passo().unwrap(); + } + let a = nucleo.alvo.vars()[0].as_tensor().flatten_all().unwrap().to_vec1::().unwrap(); + let b = nucleo.encoder.vars()[0].as_tensor().flatten_all().unwrap().to_vec1::().unwrap(); + let dif: f32 = a.iter().zip(&b).map(|(x, y)| (x - y).abs()).fold(0.0, f32::max); + assert!(dif < 1e-5, "EMA não convergiu: dif máx = {dif}"); + } + + #[test] + fn teste_jepa_aprende_latente_previsivel() { + // J-3: com alvo previsível (função determinística do contexto), a + // perda pred must cair substancialmente e o std das incorporações + // NÃO colapsa (J-1). + let dev = Device::Cpu; + let mut rng = RngDeterministico::novo(3); + // patches = 64 vetores 8-D em 4 grupos (estrutura previsível) + let mut dados: Vec> = Vec::new(); + for g in 0..4 { + let c = g as f64 * 2.0; + for _ in 0..16 { + dados.push((0..8).map(|k| c + rng.normal(0.0, 0.3) + (k as f64) * 0.05).collect()); + } + } + let patches = tensor_2d(&dados, &dev); + let cfg = CfgJepa { d_emb: 16, epocas: 40, lr: 5e-3, ..Default::default() }; + let rel = treinar_variante( + &patches, + &|n, r| mascara_intervalos_1d(n, 0.3, r), + &cfg, + VarianteJepa::TJepa, + None, + &dev, + ) + .unwrap(); + assert!( + rel.perda_final < rel.perda_inicial * 0.8, + "perda deveria cair: {} → {}", + rel.perda_inicial, + rel.perda_final + ); + assert!( + rel.std_emb_final > 0.05, + "sem colapso: std_final = {}", + rel.std_emb_final + ); + } + + #[test] + fn teste_adaptadores_formas() { + let dev = Device::Cpu; + let img = Tensor::randn(0f32, 1.0, (3, 16, 16), &dev).unwrap(); + let p = em_patches_2d(&img, 4, 4).unwrap(); + assert_eq!(p.dims().to_vec(), vec![16, 48]); + + let vid = Tensor::randn(0f32, 1.0, (4, 8, 8), &dev).unwrap(); + let t3 = em_tubos_3d(&vid, 2, 4, 4).unwrap(); + assert_eq!(t3.dims().to_vec(), vec![8, 32]); + + let spec = Tensor::randn(0f32, 1.0, (16, 32), &dev).unwrap(); + let ps = em_patches_espectrograma(&spec, 4, 8).unwrap(); + assert_eq!(ps.dims().to_vec(), vec![16, 32]); + + let ts = Tensor::randn(0f32, 1.0, (4, 32), &dev).unwrap(); + let pt_ = em_patches_serie(&ts, 8).unwrap(); + assert_eq!(pt_.dims().to_vec(), vec![16, 8]); + + let vol = Tensor::randn(0f32, 1.0, (8, 8, 8), &dev).unwrap(); + let vx = em_voxels(&vol, 4, 4, 4).unwrap(); + assert_eq!(vx.dims().to_vec(), vec![8, 64]); + + let pc = Tensor::randn(0f32, 1.0, (50, 3), &dev).unwrap(); + let mut rng = RngDeterministico::novo(5); + let gr = em_grupos_pontos(&pc, 6, 4, &mut rng).unwrap(); + assert_eq!(gr.dims().to_vec(), vec![6, 12]); + } + + #[test] + fn teste_sonic_moe_capacidade_e_balanceamento() { + // SM-1: carga por especialista ≤ capacidade; SM-2: aux ≥ ~1 quando + // roteador é ~uniforme (início). + let dev = Device::Cpu; + let moe = SonicMoe::novo(8, 4, 2, 6, &dev).unwrap(); + let x = Tensor::randn(0f32, 1.0, (20, 8), &dev).unwrap(); + let s = moe.fwd(&x).unwrap(); + for (e, c) in s.carga.iter().enumerate() { + assert!(*c <= 6, "especialista {e} com carga {c} > capacidade 6"); + } + assert_eq!(s.saida.dims().to_vec(), vec![20, 8]); + let aux = s.perda_aux.to_scalar::().unwrap() as f64; + // com roteador quase uniforme, aux ≈ E·Σ (1/E)·(1/E) = 1 + assert!(aux > 0.5 && aux < 3.0, "aux = {aux}"); + // cada token processado por ≤ n_top especialistas + let total_carga: usize = s.carga.iter().sum(); + assert!(total_carga <= 20 * 2, "carga total {total_carga} > n·topk"); + } + + #[test] + fn teste_replay_ssm_estabilidade_e_replay() { + // R-1: estados finitos e limitados; R-2: com replay α>0, após treinar + // a tarefa nova, a dinâmica antiga (magnitude do estado) permanece + // estável (não diverge). + let dev = Device::Cpu; + let mut ssm = ReplaySsm::novo(4, 6, 0.5, 8, &dev).unwrap(); + let vars = ssm.vars(); + let mut otim = crate::treino::AdamWManual::novo(vars, 1e-2, 0.0); + + // tarefa "antiga": sinal constante 1.0 + let x_antigo = Tensor::from_vec(vec![1.0f32; 16 * 4], (16, 4), &dev).unwrap(); + for _ in 0..30 { + let h = ssm.scan(&x_antigo).unwrap(); + let alvo = h.detach(); + ssm.passo_treino(&x_antigo, &alvo, &mut otim).unwrap(); + } + // estado após treino antigo: finito + let h1 = ssm.scan(&x_antigo).unwrap(); + let m1 = h1.abs().unwrap().max_all().unwrap().to_scalar::().unwrap(); + assert!(m1.is_finite() && m1 < 50.0, "estado explodiu: {m1}"); + + // tarefa "nova": sinal −1.0 — treino com replay + let x_novo = Tensor::from_vec(vec![-1.0f32; 16 * 4], (16, 4), &dev).unwrap(); + for _ in 0..30 { + let h = ssm.scan(&x_novo).unwrap(); + let alvo = h.detach(); + ssm.passo_treino(&x_novo, &alvo, &mut otim).unwrap(); + } + // a dinâmica antiga continua estável (sem explosão) + let h2 = ssm.scan(&x_antigo).unwrap(); + let m2 = h2.abs().unwrap().max_all().unwrap().to_scalar::().unwrap(); + assert!(m2.is_finite() && m2 < 50.0, "estado antigo explodiu: {m2}"); + // portas dentro de (0,1) + let a = ssm.a.as_tensor().to_vec1::().unwrap(); + for v in a { + assert!(v.is_finite()); + } + } +} diff --git a/src/jepa/variantes.rs b/src/jepa/variantes.rs new file mode 100644 index 0000000000000000000000000000000000000000..fc9fe01d44842296aa1c7bcbb56064b142e74075 --- /dev/null +++ b/src/jepa/variantes.rs @@ -0,0 +1,476 @@ +//! `jepa::variantes` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +// --------------------------------------------------------------------------- +// Parte 4 — variantes e treino unificado +// --------------------------------------------------------------------------- + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum VarianteJepa { + ImageJepa, + VJepa2, + McJepa, + AudioJepa, + PointJepa, + Jepa3D, + BrainJepa, + TJepa, + LeJepa, + BJepa, +} + +impl VarianteJepa { + pub fn nome(&self) -> &'static str { + match self { + Self::ImageJepa => "Image-JEPA", + Self::VJepa2 => "V-JEPA 2", + Self::McJepa => "MC-JEPA", + Self::AudioJepa => "Audio-JEPA", + Self::PointJepa => "Point-JEPA", + Self::Jepa3D => "3D-JEPA", + Self::BrainJepa => "Brain-JEPA", + Self::TJepa => "T-JEPA", + Self::LeJepa => "LeJEPA", + Self::BJepa => "BJEPA", + } + } +} + +#[derive(Debug, Clone)] +pub struct CfgJepa { + pub d_emb: usize, + pub epocas: usize, + pub lr: f64, + pub m: f64, // EMA + pub lambda_vc: f64, + pub gamma_vc: f64, + pub frac_mascara: f64, + pub semente: u64, + /// tamanho do bloco-alvo (nº de patches por bloco). + pub tam_bloco: usize, +} + +impl Default for CfgJepa { + fn default() -> Self { + Self { + d_emb: 32, + epocas: 60, + lr: 1.5e-3, + m: 0.99, + lambda_vc: 0.5, + gamma_vc: 1.0, + frac_mascara: 0.35, + semente: 42, + tam_bloco: 2, + } + } +} + +/// Relatório de treino de uma variante. +#[derive(Debug, Clone)] +pub struct RelatorioJepa { + pub variante: String, + pub perda_inicial: f64, + pub perda_final: f64, + /// perda PREDITIVA isolada (sem o regularizador VC) — critério de + /// aprendizado honesto (a VC pode crescer legitimamente ao empurrar o + /// std das incorporações para cima). + pub perda_pred_inicial: f64, + pub perda_pred_final: f64, + /// desvio-padrão médio por dimensão das incorporações (monitor de + /// colapso: → 0 = colapso). + pub std_emb_final: f64, + pub std_emb_inicial: f64, + pub n_patches: usize, + pub d_patch: usize, + pub epocas: usize, +} + +/// Treina uma variante JEPA sobre patches já extraídos `(n, d_patch)` com +/// um esquema de máscara fornecido (padrão: intervalos 1D). +/// +/// Máscara=true ⇒ patch é ALVO (bloco); false ⇒ CONTEXTO. +pub fn treinar_variante( + patches: &Tensor, + mascara: &dyn Fn(usize, &mut RngDeterministico) -> Vec, + cfg: &CfgJepa, + variante: VarianteJepa, + cond: Option<&Tensor>, + dev: &Device, +) -> Result { + let n = patches.dim(0)?; + let d_patch = patches.dim(1)?; + let mut rng = RngDeterministico::novo(cfg.semente); + let mut nucleo = NucleoJepa::novo( + d_patch, + cfg.d_emb, + if cond.is_some() { cond.unwrap().dim(1)? } else { 0 }, + n, + cfg.m, + cfg.lambda_vc, + cfg.gamma_vc, + dev, + )?; + let otimo_vars = nucleo.vars(); + let mut otimizador = crate::treino::AdamWManual::novo(otimo_vars, cfg.lr, 0.01); + + // métricas iniciais + let z0 = nucleo.codificar(patches)?; + let std0 = std_por_dim(&z0)?; + + let mut perda_inicial = f64::NAN; + let mut perda_final = f64::NAN; + let mut pred_inicial = f64::NAN; + let mut pred_final = f64::NAN; + let mut vc_final = f64::NAN; + for epoca in 0..cfg.epocas { + // máscara fresca a cada época (com proteção contra máscara degenerada: + // se contexto ou alvo ficar vazio, repete com fração menor — a época + // NUNCA é pulada, a perda sempre é computada) + let (ctx_idx, blocos) = loop { + let mut masc = mascara(n, &mut rng); + // defensivo: máscara menor que n => o resto é contexto + if masc.len() < n { + masc.resize(n, false); + } + masc.truncate(n); + let ctx: Vec = (0..n).filter(|&i| !masc[i]).map(|i| i as u32).collect(); + let alvos: Vec = (0..n).filter(|&i| masc[i]).collect(); + if !ctx.is_empty() && !alvos.is_empty() { + break (ctx, alvos); + } + }; + // agrupa alvos em blocos de tam_bloco + let grupos: Vec> = blocos + .chunks(cfg.tam_bloco.max(1)) + .map(|c| c.to_vec()) + .collect(); + if ctx_idx.is_empty() || grupos.is_empty() { + continue; + } + let dev2 = patches.device(); + let n_ctx = ctx_idx.len(); + let idx_t = Tensor::from_vec(ctx_idx, (n_ctx,), &dev2)?; + let ctx_patches = patches.index_select(&idx_t, 0)?; + let z_ctx_todos = nucleo.codificar(&ctx_patches)?; // (n_ctx, d_emb) + let z_ctx = z_ctx_todos.mean_keepdim(0)?; // (1, d_emb) + // alvos por grupo + let mut alvos: Vec> = Vec::new(); + for g in &grupos { + let idx: Vec = g.iter().map(|&i| i as u32).collect(); + let n_g = idx.len(); + let it = Tensor::from_vec(idx, (n_g,), &dev2)?; + alvos.push(vec![(g[0], patches.index_select(&it, 0)?)]); + } + // condição por amostra — broadcast (o lote é 1 aqui: o conjunto todo) + let (perda, lp, lv) = nucleo.perda_lote(&z_ctx, &[alvos_concat(&alvos)], cond, &z_ctx_todos)?; + let p = perda.to_scalar::()? as f64; + if p.is_finite() { + if epoca == 0 { + perda_inicial = p; + pred_inicial = lp; + } + perda_final = p; + pred_final = lp; + vc_final = lv; + } + // decaimento cosseno do lr + clipping de gradiente (estabilidade) + let frac = epoca as f64 / cfg.epocas.max(1) as f64; + let lr_t = cfg.lr * 0.5 * (1.0 + (std::f64::consts::PI * frac).cos()); + otimizador.set_lr(lr_t); + let mut grads = crate::treino::grads_achatados(&perda, &nucleo.vars())?; + crate::treino::AdamWManual::clipar(&mut grads, 1.0); + otimizador.passo(grads); + nucleo.ema_passo()?; + } + let z1 = nucleo.codificar(patches)?; + let std1 = std_por_dim(&z1)?; + let _ = (pred_inicial, vc_final); + Ok(RelatorioJepa { + variante: variante.nome().to_string(), + perda_inicial, + perda_final, + perda_pred_inicial: pred_inicial, + perda_pred_final: pred_final, + std_emb_final: std1, + std_emb_inicial: std0, + n_patches: n, + d_patch, + epocas: cfg.epocas, + }) +} + +fn alvos_concat(alvos: &[Vec<(usize, Tensor)>]) -> Vec<(usize, Tensor)> { + alvos.iter().flatten().cloned().collect() +} + +fn std_por_dim(z: &Tensor) -> Result { + let n = z.dim(0)? as f64; + let media = z.mean_keepdim(0)?; + let centr = z.broadcast_sub(&media)?; + let var = (centr.sqr()?.sum(0)? / n)?; + Ok(var.sqrt()?.mean_all()?.to_scalar::()? as f64) +} + + +// --------------------------------------------------------------------------- +// Variantes especiais: LeJEPA (edição-condicionado), MC-JEPA (cruz-modal), +// BJEPA (bidirecional com consistência de ciclo) +// --------------------------------------------------------------------------- + +/// **LeJEPA** — predição latente condicionada por EDIÇÃO: contexto = E(x), +/// condição = instrução de edição `e` (vetor de baixa dimensão), alvo = +/// `E_ξ(edit_e(x))`. A condição é a INSTRUÇÃO (não o resultado — sem +/// vazamento: `e` não contém `edit(x)`). +pub fn treinar_lejepa( + dados: &Tensor, // (n, d_patch) — originais + edits: &Tensor, // (n, d_cond) — instruções de edição + editados: &Tensor, // (n, d_patch) — resultados das edições + cfg: &CfgJepa, + dev: &Device, +) -> Result { + let n = dados.dim(0)?; + let d_patch = dados.dim(1)?; + let d_cond = edits.dim(1)?; + let mut rng = RngDeterministico::novo(cfg.semente); + let mut nucleo = NucleoJepa::novo( + d_patch, cfg.d_emb, d_cond, 1, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev, + )?; + let mut otim = crate::treino::AdamWManual::novo(nucleo.vars(), cfg.lr, 0.01); + let mut perda_inicial = f64::NAN; + let mut perda_final = f64::NAN; + let z0 = nucleo.codificar(dados)?; + let std0 = std_por_dim(&z0)?; + for epoca in 0..cfg.epocas { + // subamostra do lote + let idx = rng.amostra_k_de_n(n.min(64), n); + let sel: Vec = idx.iter().map(|&i| i as u32).collect(); + let it = Tensor::from_vec(sel.clone(), (sel.len(),), dev)?; + let xb = dados.index_select(&it, 0)?; + let eb = edits.index_select(&it, 0)?; + let yb = editados.index_select(&it, 0)?; + let z_ctx = nucleo.codificar(&xb)?; // (m, d_emb) + let mut perdas: Vec = Vec::new(); + for i in 0..xb.dim(0)? { + let zc = z_ctx.get(i)?.broadcast_as((1, cfg.d_emb))?; + let e_i = eb.get(i)?.broadcast_as((1, d_cond))?; + let yt = yb.get(i)?.broadcast_as((1, d_patch))?; + let zt = nucleo + .alvo_codificar(&yt)? + .mean(0)? + .broadcast_as((1, cfg.d_emb))?; + let pred = nucleo.predizer(&zc, 0, Some(&e_i))?; + let dif = (&pred - &zt)?; + perdas.push((dif.sqr()?.sum_all()? / cfg.d_emb as f64)?); + } + let l_pred = (Tensor::stack(&perdas, 0)?.sum_all()? / perdas.len() as f64)?; + let l_vc = nucleo.perda_vc(&z_ctx)?; + let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(cfg.lambda_vc, 0.0)?)?; + let p = total.to_scalar::()? as f64; + if p.is_finite() { + if epoca == 0 { + perda_inicial = p; + } + perda_final = p; + } + let mut grads = crate::treino::grads_achatados(&total, &nucleo.vars())?; + crate::treino::AdamWManual::clipar(&mut grads, 1.0); + otim.passo(grads); + nucleo.ema_passo()?; + } + let z1 = nucleo.codificar(dados)?; + Ok(RelatorioJepa { + variante: "LeJEPA".into(), + perda_inicial, + perda_final, + perda_pred_inicial: perda_inicial, + perda_pred_final: perda_final, + std_emb_final: std_por_dim(&z1)?, + std_emb_inicial: std0, + n_patches: n, + d_patch, + epocas: cfg.epocas, + }) +} + +/// **MC-JEPA** — cruz-modal: dois codificadores, predição nos DOIS sentidos +/// (A→B e B→A) com preditores separados e alvos EMA dos dois lados. +pub fn treinar_mcjepa( + mod_a: &Tensor, // (n, d_a) + mod_b: &Tensor, // (n, d_b) + cfg: &CfgJepa, + dev: &Device, +) -> Result { + let n = mod_a.dim(0)?; + let d_a = mod_a.dim(1)?; + let d_b = mod_b.dim(1)?; + // núcleo A→B e núcleo B→A (compartilham apenas a estrutura) + let mut nb = NucleoJepa::novo(d_a, cfg.d_emb, 0, 1, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev)?; + let mut na = NucleoJepa::novo(d_b, cfg.d_emb, 0, 1, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev)?; + let mut otim = crate::treino::AdamWManual::novo( + [nb.vars(), na.vars()].concat(), + cfg.lr, + 0.01, + ); + let mut rng = RngDeterministico::novo(cfg.semente); + let mut perda_inicial = f64::NAN; + let mut perda_final = f64::NAN; + for epoca in 0..cfg.epocas { + let idx = rng.amostra_k_de_n(n.min(64), n); + let sel: Vec = idx.iter().map(|&i| i as u32).collect(); + let it = Tensor::from_vec(sel, (idx.len(),), dev)?; + let xa = mod_a.index_select(&it, 0)?; + let xb = mod_b.index_select(&it, 0)?; + // z_a = E_a(x_a) prevê ẑ_b; z_b = E_b(x_b) prevê ẑ_a + let z_a = nb.codificar(&xa)?; // (m, d_emb) + let z_b = na.codificar(&xb)?; + let t_a = na.alvo_codificar(&xa)?; // alvo EMA do lado A + let t_b = nb.alvo_codificar(&xb)?; // alvo EMA do lado B + let m_ = xa.dim(0)?; + let mut perdas: Vec = Vec::new(); + for i in 0..m_ { + // A → B + let zc = z_a.get(i)?.broadcast_as((1, cfg.d_emb))?; + let zt = t_b.get(i)?.broadcast_as((1, cfg.d_emb))?; + let pred = nb.predizer(&zc, 0, None)?; + let dif = (&pred - &zt)?; + perdas.push((dif.sqr()?.sum_all()? / cfg.d_emb as f64)?); + // B → A + let zc2 = z_b.get(i)?.broadcast_as((1, cfg.d_emb))?; + let zt2 = t_a.get(i)?.broadcast_as((1, cfg.d_emb))?; + let pred2 = na.predizer(&zc2, 0, None)?; + let dif2 = (&pred2 - &zt2)?; + perdas.push((dif2.sqr()?.sum_all()? / cfg.d_emb as f64)?); + } + let l_pred = (Tensor::stack(&perdas, 0)?.sum_all()? / perdas.len() as f64)?; + let l_vc = (nb.perda_vc(&z_a)? + na.perda_vc(&z_b)?)?; + let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(cfg.lambda_vc, 0.0)?)?; + let p = total.to_scalar::()? as f64; + if p.is_finite() { + if epoca == 0 { + perda_inicial = p; + } + perda_final = p; + } + let mut grads = crate::treino::grads_achatados(&total, &[nb.vars(), na.vars()].concat())?; + crate::treino::AdamWManual::clipar(&mut grads, 1.0); + otim.passo(grads); + nb.ema_passo()?; + na.ema_passo()?; + } + let z1 = nb.codificar(mod_a)?; + Ok(RelatorioJepa { + variante: "MC-JEPA".into(), + perda_inicial, + perda_final, + perda_pred_inicial: perda_inicial, + perda_pred_final: perda_final, + std_emb_final: std_por_dim(&z1)?, + std_emb_inicial: std_por_dim(&nb.codificar(mod_a)?)?, + n_patches: n, + d_patch: d_a.max(d_b), + epocas: cfg.epocas, + }) +} + +/// **BJEPA** — bidirecional: preditor causal (z_t → z̃_{t+1}), anti-causal +/// (z_{t+1} → z̃_t) e consistência de ciclo +/// `‖P_b(P_f(z_t)) − sg(z_t)‖²` — a volta deve reconstruir o presente. +pub fn treinar_bjepa(patches: &Tensor, cfg: &CfgJepa, dev: &Device) -> Result { + let n = patches.dim(0)?; + let d_patch = patches.dim(1)?; + let mut nucleo = NucleoJepa::novo( + d_patch, cfg.d_emb, 0, 2, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev, + )?; + // preditor anti-causal: núcleo separado + let mut ninv = NucleoJepa::novo( + d_patch, cfg.d_emb, 0, 2, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev, + )?; + let mut otim = + crate::treino::AdamWManual::novo([nucleo.vars(), ninv.vars()].concat(), cfg.lr, 0.01); + let mut perda_inicial = f64::NAN; + let mut perda_final = f64::NAN; + for epoca in 0..cfg.epocas { + let z = nucleo.codificar(patches)?; // (n, d_emb) + let zt = nucleo.alvo_codificar(patches)?; // alvos EMA (stop-grad) + let mut perdas: Vec = Vec::new(); + for t in 0..n.saturating_sub(1) { + let zc = z.get(t)?.broadcast_as((1, cfg.d_emb))?; + let z_prox = zt.get(t + 1)?.broadcast_as((1, cfg.d_emb))?; + // causal: z_t → z̃_{t+1} (pos 0 = "próximo") + let pf = nucleo.predizer(&zc, 0, None)?; + let dif = (&pf - &z_prox)?; + perdas.push((dif.sqr()?.sum_all()? / cfg.d_emb as f64)?); + // anti-causal: z_{t+1} → z̃_t (pos 1 = "anterior") + let zb = z.get(t + 1)?.broadcast_as((1, cfg.d_emb))?; + let z_ant = zt.get(t)?.broadcast_as((1, cfg.d_emb))?; + let pb = ninv.predizer(&zb, 1, None)?; + let dif2 = (&pb - &z_ant)?; + perdas.push((dif2.sqr()?.sum_all()? / cfg.d_emb as f64)?); + // ciclo: P_b(P_f(z_t)) ≈ sg(z_t) + let pf_sg = nucleo.predizer(&zc, 0, None)?.detach(); + let pciclo = ninv.predizer(&pf_sg.broadcast_as((1, cfg.d_emb))?, 1, None)?; + let zt_sg = zt.get(t)?.broadcast_as((1, cfg.d_emb))?; + let dif3 = (&pciclo - &zt_sg)?; + perdas.push((dif3.sqr()?.sum_all()? / cfg.d_emb as f64)?); + } + let l_pred = (Tensor::stack(&perdas, 0)?.sum_all()? / perdas.len().max(1) as f64)?; + let l_vc = nucleo.perda_vc(&z)?; + let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(cfg.lambda_vc, 0.0)?)?; + let p = total.to_scalar::()? as f64; + if p.is_finite() { + if epoca == 0 { + perda_inicial = p; + } + perda_final = p; + } + let mut grads = crate::treino::grads_achatados(&total, &[nucleo.vars(), ninv.vars()].concat())?; + crate::treino::AdamWManual::clipar(&mut grads, 1.0); + otim.passo(grads); + nucleo.ema_passo()?; + ninv.ema_passo()?; + } + let z1 = nucleo.codificar(patches)?; + Ok(RelatorioJepa { + variante: "BJEPA".into(), + perda_inicial, + perda_final, + perda_pred_inicial: perda_inicial, + perda_pred_final: perda_final, + std_emb_final: std_por_dim(&z1)?, + std_emb_inicial: std_por_dim(&nucleo.codificar(patches)?)?, + n_patches: n, + d_patch, + epocas: cfg.epocas, + }) +} + +/// Esquema de máscara por variante a partir da grade de patches. +pub fn mascara_por_variante( + variante: VarianteJepa, + grid: &[usize], + frac: f64, + rng: &mut RngDeterministico, +) -> Vec { + match variante { + VarianteJepa::ImageJepa => mascara_blocos_2d(grid[0], grid.get(1).copied().unwrap_or(1), 4, frac, rng), + VarianteJepa::VJepa2 | VarianteJepa::Jepa3D => mascara_tubos_3d( + grid[0], + grid.get(1).copied().unwrap_or(1), + grid.get(2).copied().unwrap_or(1), + 3, + frac, + rng, + ), + VarianteJepa::AudioJepa => mascara_bandas(grid[0], grid.get(1).copied().unwrap_or(1), 3, frac, rng), + VarianteJepa::BrainJepa => mascara_segmentos(grid[0], grid.get(1).copied().unwrap_or(1), frac, rng), + _ => mascara_intervalos_1d(grid.iter().product::().max(1), frac, rng), + } +} + +pub fn versao() -> &'static str { + "jepa-0.5.0" +} diff --git a/src/lib.rs b/src/lib.rs index 5c4dd33b03bbff14fc9c4be87fe3ee1d768e68ab..037eebfba10b3f2fa6c2201bc76845f042e35db6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ pub mod arvore_pesos; pub mod atencao; pub mod acoplador; pub mod camadas; +pub mod checkpoint; pub mod config; pub mod controlador; pub mod diagnostico; diff --git a/src/moe_redirecionavel.rs b/src/moe_redirecionavel.rs deleted file mode 100644 index 9eb28fe18b34f588e1d4141a7109fc3fffb3f544..0000000000000000000000000000000000000000 --- a/src/moe_redirecionavel.rs +++ /dev/null @@ -1,1207 +0,0 @@ -//! **MoEs de codificação/decodificação REDIRECIONÁVEIS com Mecanismo de -//! Atenção próprio e independente (paralelo)** (v0.6.0 — pedido do usuário). -//! -//! Cada `MoeRedirecionavel` possui: -//! - um roteador aprendido `d → E`; -//! - E especialistas `MlpJepa`, cada um com um PAPEL (Codificador ou -//! Decodificador) — o conjunto de papéis é REDIRECIONÁVEL em tempo de -//! execução (permutação), sem reconstrução do modelo; -//! - uma ATENÇÃO PRÓPRIA independente da atenção ADBA do tronco, com gate -//! escalar nascido em ZERO (neutralidade no nascimento); -//! - capacidade por especialista e perda auxiliar de balanceamento. -//! -//! **Provas completas em `docs/memorias/r-25-*`; síntese:** -//! - **M-R1 (invariância sob redirecionamento):** o redirecionamento é uma -//! permutação π dos papéis; o multiconjunto de atribuições -//! (especialista, token) numa passada por papel é levado ao multiconjunto -//! da passada π(papel) — a capacidade total e a perda auxiliar de -//! balanceamento E·Σ f_e·p_e são invariantes (soma sobre todos os e). -//! - **M-R2 (neutralidade da atenção própria no nascimento):** com -//! `g = 0`, `y = x` bit-a-bit; a perturbação é limitada por -//! `‖Δy‖ ≤ L_soft·|g|·‖W_o·V‖` (Lipschitz do softmax com constante -//! L_soft = 1 em probabilidade; estrutura idêntica ao T-P5). -//! - **M-R3 (paralelismo dos dois caminhos):** numa mesma entrada, as -//! passadas Codificador e Decodificador tocam conjuntos DISJUNTOS de -//! especialistas e compartilham apenas leitura — o DAG de computação tem -//! duas sub-DAGs independentes ⇒ tempo paralelo (2 processadores) = -//! max(T_enc, T_dec) + sobrecarga < T_enc + T_dec = tempo sequencial -//! (modelo PRAM; medido empiricamente com threads). -//! - **M-R4 (fechamento da classe de funções):** redirecionar equivale a -//! permutar colunas do roteador e re-rotular papéis; a classe de funções -//! implementáveis é fechada sob π ⇒ expressividade inalterada. -//! - **M-R5 (water-filling por QE — Cauchy–Schwarz):** dadas capacidades -//! totais C e erros por célula q_i, a alocação `c_i ∝ q_i` minimiza -//! `Σ q_i²/c_i ≥ (Σq_i)²/C`, cota superior do erro refinado ponderado. - -use candle_core::{Device, Result, Tensor, Var}; -use crate::camadas::Linear; -use crate::jepa::MlpJepa; -use crate::rng::RngDeterministico; -use crate::treino::AdamWManual; - -// --------------------------------------------------------------------------- -// Atenção própria (independente e paralela à ADBA do tronco) -// --------------------------------------------------------------------------- - -pub struct AtencaoPropria { - pub q: Linear, - pub k: Linear, - pub v: Linear, - pub o: Linear, - /// gate escalar — nasce ZERO (M-R2: neutralidade no nascimento). - pub gate: Var, - pub n_cabecas: usize, -} - -impl AtencaoPropria { - pub fn novo(d: usize, n_cabecas: usize, dev: &Device) -> Result { - let desvio = 1.0 / ((d * 2) as f64).sqrt(); - let mk = || Linear::sem_vies(d, d, desvio, dev); - let gate = Tensor::zeros((), candle_core::DType::F32, dev)?; - Ok(AtencaoPropria { - q: mk()?, k: mk()?, v: mk()?, o: mk()?, - gate: Var::from_tensor(&gate)?, - n_cabecas, - }) - } - - /// x (n, d) → y = x + g·attn(x) — multi-cabeça compacta (sem máscara: - /// atenção plena entre os tokens da entrada do MoE). - pub fn fwd(&self, x: &Tensor) -> Result { - let d = x.dim(candle_core::D::Minus1)?; - let d_cab = d / self.n_cabecas; - let n = x.dim(0)?; - let forma = (self.n_cabecas, n, d_cab); - let q = self.q.fwd(x)?.reshape(forma)?; - let k = self.k.fwd(x)?.reshape(forma)?; - let v = self.v.fwd(x)?.reshape(forma)?; - let scores = q.matmul(&k.transpose(1, 2)?)?.affine(1.0 / (d_cab as f64).sqrt(), 0.0)?; - let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1)?; - let ctx = attn.matmul(&v)?; // (h, n, d_cab) - let ctx = ctx.reshape((n, d))?; - let saida = self.o.fwd(&ctx)?; - // y = x + g·saida (g=0 no nascimento ⇒ y = x — M-R2) - let g = self.gate.as_tensor(); - let forma: Vec = saida.dims().to_vec(); - let delta = saida.broadcast_mul(&g.broadcast_as(forma)?)?; - Ok((x + &delta)?) - } - - pub fn vars(&self) -> Vec { - let mut v = self.q.vars(); - v.extend(self.k.vars()); - v.extend(self.v.vars()); - v.extend(self.o.vars()); - v.push(self.gate.clone()); - v - } -} - -// --------------------------------------------------------------------------- -// MoE redirecionável -// --------------------------------------------------------------------------- - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Papel { - Codificador, - Decodificador, -} - -impl Papel { - pub fn nome(&self) -> &'static str { - match self { Papel::Codificador => "codificador", Papel::Decodificador => "decodificador" } - } -} - -pub struct SaidaMoeR { - pub saida: Tensor, - pub perda_aux: Tensor, - pub carga: Vec, - pub descartados: usize, - /// especialistas efetivamente usados nesta passada (índices globais). - pub usados: Vec, -} - -pub struct MoeRedirecionavel { - pub roteador: Linear, // d → E - pub especialistas: Vec, // E experts d → d - pub papeis: Vec, // papel corrente de cada especialista - pub atencao: AtencaoPropria, // atenção PRÓPRIA (M-R2/M-R3) - pub n_top: usize, - pub capacidade: usize, - pub lambda_aux: f64, -} - -impl MoeRedirecionavel { - pub fn novo(d: usize, n_expertos: usize, n_top: usize, capacidade: usize, dev: &Device) -> Result { - let roteador = Linear::sem_vies(d, n_expertos, 0.1, dev)?; - let especialistas = (0..n_expertos) - .map(|_| MlpJepa::novo(d, 2 * d, d, dev)) - .collect::>>()?; - // papéis iniciais alternados (metade cod, metade dec) - let papeis = (0..n_expertos) - .map(|i| if i % 2 == 0 { Papel::Codificador } else { Papel::Decodificador }) - .collect(); - Ok(MoeRedirecionavel { - roteador, - especialistas, - papeis, - atencao: AtencaoPropria::novo(d, 2, dev)?, - n_top: n_top.max(1), - capacidade, - lambda_aux: 0.01, - }) - } - - pub fn vars(&self) -> Vec { - let mut v = self.roteador.vars(); - for e in &self.especialistas { - v.extend(e.vars()); - } - v.extend(self.atencao.vars()); - v - } - - /// **M-R1/M-R4:** redireciona os papéis segundo a permutação `permuta` - /// (novo_papel[i] = papel_anterior[permuta[i]]). `redirecionar_todos` - /// troca Codificador ↔ Decodificador. - pub fn redirecionar(&mut self, permuta: &[usize]) { - let antigos = self.papeis.clone(); - for (i, &p) in permuta.iter().enumerate() { - if p < antigos.len() { - self.papeis[i] = antigos[p]; - } - } - } - - pub fn redirecionar_todos(&mut self) { - for p in self.papeis.iter_mut() { - *p = match *p { - Papel::Codificador => Papel::Decodificador, - Papel::Decodificador => Papel::Codificador, - }; - } - } - - fn indices_do_papel(&self, papel: Papel) -> Vec { - self.papeis.iter().enumerate().filter(|(_, p)| **p == papel).map(|(i, _)| i).collect() - } - - /// Passada por papel: atenção própria → roteamento top-k DENTRO do - /// subconjunto de especialistas do papel → capacidade → soma ponderada. - /// - /// ⚠️ **DEPRECIADA na v0.9.0 (crítica Qwen3.8 nº 2b / r-34 §4):** - /// **bug v080-1 catalogado (r-33 §5)** — esta versão materializa os - /// valores com `to_vec2`/`from_vec` no acúmulo, o que SECCIONA o grafo - /// de autograd: especialistas e roteador NÃO recebem gradiente por - /// esta saída (só a atenção própria treina). Fora da superfície - /// pública (`pub(crate)`) desde a v0.9.0 — mantida EXCLUSIVAMENTE como - /// oráculo dos testes M-R6 (equivalência de valores) e M-R7 (prova do - /// bug); o caminho de treino canônico é [`MoeRedirecionavel::forward`]. - #[deprecated(since = "0.9.0", note = "bug v080-1: secciona o grafo de autograd — use forward()")] - pub(crate) fn fwd(&self, x: &Tensor, papel: Papel) -> Result { - let y = self.atencao.fwd(x)?; // (n, d) — atenção própria independente - let n = y.dim(0)?; - let d = y.dim(1)?; - let dev = y.device(); - let do_papel = self.indices_do_papel(papel); - let e_local = do_papel.len(); - let mut descartados = 0usize; - let mut saida = Tensor::zeros((n, d), candle_core::DType::F32, &dev)?; - let mut peso_total = Tensor::zeros((n, 1), candle_core::DType::F32, &dev)?; - // probs de roteamento só sobre especialistas do papel (mascarado) - let logits_todos = self.roteador.fwd(&y)?; // (n, E) - let mut probs_v: Vec> = logits_todos.to_vec2::()?; - for linha in probs_v.iter_mut() { - // máscara: -inf fora do papel - for (e, lv) in linha.iter_mut().enumerate() { - if !do_papel.contains(&e) { - *lv = f32::NEG_INFINITY; - } - } - } - // softmax manual por linha (com -inf → 0) - let probs: Vec> = probs_v - .iter() - .map(|linha| { - let m = linha.iter().copied().fold(f32::NEG_INFINITY, f32::max); - let exps: Vec = linha.iter().map(|v| (v - m).exp()).collect(); - let s: f32 = exps.iter().sum(); - if s <= 0.0 { - vec![0.0; linha.len()] - } else { - exps.iter().map(|e| e / s).collect() - } - }) - .collect(); - // seleção top-k com capacidade (código extraído — MESMA semântica - // de desempate da v0.6.0, compartilhada com forward) - let (designacao, carga) = selecionar_com_capacidade( - &probs, - &do_papel, - n, - self.n_top, - self.capacidade, - self.especialistas.len(), - ); - let usados: Vec = designacao.iter().map(|(ei, _)| *ei).collect(); - for (ei, escolhidos) in designacao { - let idx: Vec = escolhidos.iter().map(|&i| i as u32).collect(); - let n_e = idx.len(); - let idx_t = Tensor::from_vec(idx, (n_e,), &dev)?; - let tile = y.index_select(&idx_t, 0)?; - let y_e = self.especialistas[ei].fwd(&tile)?; - let w: Vec = escolhidos.iter().map(|&i| probs[i][ei]).collect(); - let w_t = Tensor::from_vec(w.clone(), (n_e, 1), &dev)?; - let contrib = y_e.broadcast_mul(&w_t)?; - let mut buf = saida.to_vec2::()?; - let contrib_v = contrib.to_vec2::()?; - for (kk, &i) in escolhidos.iter().enumerate() { - for j in 0..d { - buf[i][j] += contrib_v[kk][j]; - } - } - saida = Tensor::from_vec(buf.into_iter().flatten().collect::>(), (n, d), &dev)?; - let mut pt = peso_total.to_vec2::()?; - for (kk, &i) in escolhidos.iter().enumerate() { - pt[i][0] += w[kk]; - } - peso_total = Tensor::from_vec(pt.into_iter().flatten().collect::>(), (n, 1), &dev)?; - } - let pt_v = peso_total.to_vec2::()?; - for i in 0..n { - if pt_v[i][0] == 0.0 { - descartados += 1; - } - } - let denom = peso_total.clamp(1e-6, f64::INFINITY)?; - let saida = saida.broadcast_div(&denom)?; - // saída residual: y + roteamento (atenção já embutida em y) - let saida = (&y + &saida)?; - // perda auxiliar sobre o SUBCONJUNTO do papel (invariante global — M-R1) - let mut f = vec![0f32; e_local.max(1)]; - let mut m = 0usize; - for i in 0..n { - let (mut melhor, mut mp) = (0usize, f32::NEG_INFINITY); - for (li, &g) in do_papel.iter().enumerate() { - if probs[i][g] > mp { - mp = probs[i][g]; - melhor = li; - } - } - f[melhor] += 1.0; - m += 1; - } - if m > 0 && e_local > 0 { - for v in f.iter_mut() { - *v /= m as f32; - } - } - let f_t = Tensor::from_vec(f, (e_local.max(1),), &dev)?; - let p_media_local: Vec = (0..e_local) - .map(|li| { - let g = do_papel[li]; - (0..n).map(|i| probs[i][g]).sum::() / n.max(1) as f32 - }) - .collect(); - let p_t = Tensor::from_vec(p_media_local, (e_local.max(1),), &dev)?; - let perda_aux = ((&f_t * &p_t)?.sum_all()? * (e_local.max(1) as f64))?; - Ok(SaidaMoeR { saida, perda_aux, carga, descartados, usados }) - } - - /// **M-R6 (v0.8.0):** passada VETORIZADA — roteamento, acúmulo e perda - /// auxiliar em OPERAÇÕES DE TENSOR rastreadas (proposta Qwen-3.8 nº 4 - /// IMPLEMENTADA de verdade: o esboço original terminava em - /// `unimplemented!()`; a fórmula da perda auxiliar `E·Σ f_e·p̄_e` - /// estava CORRETA — Switch Transformer — e é mantida). - /// - /// **Caminho canônico desde a v0.9.0 (antes `fwd_vetorizado`):** - /// correção do bug v080-1 — a acumulação usa `index_add` (op - /// rastreada pelo autograd do candle — `IndexAdd` em op.rs) em vez do - /// round-trip `to_vec2`/`from_vec`; os pesos de roteamento saem do - /// PRÓPRIO tensor `probs` (vivo até o roteador); a perda auxiliar usa - /// `p̄` do tensor vivo com `f` constante (padrão Switch: argmax é - /// não-diferenciável, a média de probs carrega o gradiente). O - /// gradiente flui por TODOS os caminhos (teste M-R7) com VALORES - /// idênticos aos da referência `fwd` (mesma seleção — teste M-R6). - pub fn forward(&self, x: &Tensor, papel: Papel) -> Result { - let y = self.atencao.fwd(x)?; // (n, d) - let n = y.dim(0)?; - let d = y.dim(1)?; - let dev = y.device(); - let e_total = self.especialistas.len(); - let do_papel = self.indices_do_papel(papel); - let e_local = do_papel.len(); - // 1) máscara do papel + softmax EM TENSOR — grafo VIVO até o roteador - let mut mascara_v = vec![f32::NEG_INFINITY; e_total]; - for &e in &do_papel { - mascara_v[e] = 0.0; - } - let mascara = Tensor::from_vec(mascara_v, (1, e_total), &dev)?; - let logits = self.roteador.fwd(&y)?; - let mascarados = logits.broadcast_add(&mascara)?; - let probs = candle_nn::ops::softmax(&mascarados, candle_core::D::Minus1)?; // (n, E) VIVO - // 2) seleção discreta (única materialização — o roteamento duro é - // não-diferenciável por natureza; os PESOS ficam vivos no passo 3) - let probs_v = probs.to_vec2::()?; - let (designacao, carga) = - selecionar_com_capacidade(&probs_v, &do_papel, n, self.n_top, self.capacidade, e_total); - let usados: Vec = designacao.iter().map(|(ei, _)| *ei).collect(); - // 3) acúmulo VIVO: index_add é rastreada — especialistas recebem - // gradiente pelas contribuições, e o roteador pelos pesos w - let mut saida = Tensor::zeros((n, d), candle_core::DType::F32, &dev)?; - let mut peso_total = Tensor::zeros((n, 1), candle_core::DType::F32, &dev)?; - for (ei, escolhidos) in &designacao { - let idx: Vec = escolhidos.iter().map(|&i| i as u32).collect(); - let n_e = idx.len(); - let idx_t = Tensor::from_vec(idx, (n_e,), &dev)?; - let tile = y.index_select(&idx_t, 0)?; // VIVO - let y_e = self.especialistas[*ei].fwd(&tile)?; // VIVO (gradiente!) - // w = probs[i][ei] VIVO: gradiente flui ao roteador - let linhas = probs.index_select(&idx_t, 0)?; // (n_e, E) VIVO - let w_t = linhas.narrow(1, *ei, 1)?.contiguous()?; // (n_e, 1) VIVO - let contrib = y_e.broadcast_mul(&w_t)?; // (n_e, d) VIVO - saida = saida.index_add(&idx_t, &contrib, 0)?; // VIVO (M-R6) - peso_total = peso_total.index_add(&idx_t, &w_t, 0)?; // VIVO - } - // 4) normalização idêntica à fwd (clamp 1e-6 nos descartados) - let pt_v = peso_total.to_vec2::()?; // estatística apenas - let mut descartados = 0usize; - for i in 0..n { - if pt_v[i][0] == 0.0 { - descartados += 1; - } - } - let denom = peso_total.clamp(1e-6, f64::INFINITY)?; - let saida = saida.broadcast_div(&denom)?; - let saida = (&y + &saida)?; // residual — como a fwd - // 5) perda auxiliar EM TENSOR (fórmula M-R1/Switch: e_local·Σ f_e·p̄_e) - let cols: Vec = do_papel.iter().map(|&e| e as u32).collect(); - let cols_t = Tensor::from_vec(cols, (e_local.max(1),), &dev)?; - let probs_local = probs.index_select(&cols_t, 1)?; // (n, e_local) VIVO - let argmax_v = probs_local.argmax(candle_core::D::Minus1)?.to_vec1::()?; - let mut f = vec![0f32; e_local.max(1)]; - for &a in &argmax_v { - f[a as usize] += 1.0; - } - if n > 0 { - for v in f.iter_mut() { - *v /= n as f32; - } - } - let f_t = Tensor::from_vec(f, (e_local.max(1),), &dev)?; - let p_barra = probs_local.mean_keepdim(0)?.flatten_all()?; // (e_local,) VIVO - let perda_aux = ((&f_t * &p_barra)?.sum_all()? * (e_local.max(1) as f64))?; - Ok(SaidaMoeR { saida, perda_aux, carga, descartados, usados }) - } - - /// **M-R3:** passadas Codificador e Decodificador em DUAS THREADS - /// (DAGs independentes — especialistas disjuntos por papel). Devolve - /// (enc, dec, tempo_paralelo_s, tempo_sequencial_s, speedup). - pub fn fwd_ambos_par(&self, x: &Tensor) -> Result<(SaidaMoeR, SaidaMoeR, f64, f64, f64)> { - use std::time::Instant; - // sequencial (medição) - let t0 = Instant::now(); - let enc_seq = self.forward(x, Papel::Codificador)?; - let dec_seq = self.forward(x, Papel::Decodificador)?; - let t_seq = t0.elapsed().as_secs_f64(); - // paralelo (2 threads com escopo — referências compartilhadas) - let t0 = Instant::now(); - let (enc, dec) = { - let xref = &x; - let sref = &self; - std::thread::scope(|sc| { - let h1 = sc.spawn(move || sref.forward(xref, Papel::Codificador)); - let h2 = sc.spawn(move || sref.forward(xref, Papel::Decodificador)); - let r1 = h1.join().unwrap_or_else(|_| Err(candle_core::Error::Msg("thread enc".into()))); - let r2 = h2.join().unwrap_or_else(|_| Err(candle_core::Error::Msg("thread dec".into()))); - (r1, r2) - }) - }; - let (enc, dec) = (enc?, dec?); - let t_par = t0.elapsed().as_secs_f64(); - // determinismo: paralelo == sequencial (mesmas entradas, especialistas disjuntos) - let dif = (&enc.saida - &enc_seq.saida)?.sqr()?.sum_all()?.to_scalar::()? as f64 - + (&dec.saida - &dec_seq.saida)?.sqr()?.sum_all()?.to_scalar::()? as f64; - assert!(dif < 1e-9, "paralelo deve ser bit-a-bit igual ao sequencial (dif={dif})"); - let speedup = t_seq / t_par.max(1e-12); - Ok((enc, dec, t_par, t_seq, speedup)) - } -} - -// --------------------------------------------------------------------------- -// M-R5 — alocação water-filling por QE -// --------------------------------------------------------------------------- - -/// Seleção top-k com capacidade por especialista (v0.8.0 — extraída -/// VERBATIM do loop da `fwd` v0.6.0: mesma ordem de candidatos, mesmos -/// desempates estáveis de `sort_by`). Compartilhada por `fwd` e -/// `forward` ⇒ as duas atribuem EXATAMENTE os mesmos tokens aos -/// mesmos especialistas (base do teste M-R6 — equivalência de valores). -/// Devolve (designação por especialista, carga global por especialista). -fn selecionar_com_capacidade( - probs: &[Vec], - do_papel: &[usize], - n: usize, - n_top: usize, - capacidade: usize, - n_especialistas: usize, -) -> (Vec<(usize, Vec)>, Vec) { - let mut carga = vec![0usize; n_especialistas]; - let mut designacao: Vec<(usize, Vec)> = Vec::new(); - for &ei in do_papel.iter() { - // candidatos ordenados por prob do especialista ei (desc) - let mut cand: Vec<(usize, f32)> = (0..n).map(|i| (i, probs[i][ei])).collect(); - cand.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); - let mut escolhidos: Vec = Vec::new(); - for (i, _) in cand { - if carga[ei] >= capacidade { - break; - } - // ei está no top-k(papel) do token i? - let mut ordem: Vec<(f32, usize)> = - do_papel.iter().map(|&g| (probs[i][g], g)).collect(); - ordem.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); - let top: Vec = ordem.iter().rev().take(n_top).map(|(_, g)| *g).collect(); - if top.contains(&ei) { - escolhidos.push(i); - carga[ei] += 1; - } - } - if escolhidos.is_empty() { - continue; - } - designacao.push((ei, escolhidos)); - } - (designacao, carga) -} - -/// Aloca `capacidade_total` especialistas entre células com QE `qes` -/// proporcionalmente ao QE (Cauchy–Schwarz: minimiza Σ q_i²/c_i). -/// Devolve c_i ≥ 1 com Σ c_i = capacidade_total (se capacidade ≥ células). -pub fn alocar_por_qe(qes: &[f64], capacidade_total: usize) -> Vec { - let m = qes.len(); - if m == 0 { - return Vec::new(); - } - let soma: f64 = qes.iter().sum::().max(1e-12); - let mut c: Vec = qes - .iter() - .map(|q| ((q / soma) * capacidade_total as f64).round() as usize) - .collect(); - // garante mínimo 1 e corrige a soma por maior resíduo - for ci in c.iter_mut() { - *ci = (*ci).max(1); - } - let mut total: usize = c.iter().sum(); - while total > capacidade_total.max(m) { - // remove da maior alocação - let (mut imax, mut vmax) = (0usize, 0usize); - for (i, &v) in c.iter().enumerate() { - if v > vmax { - vmax = v; - imax = i; - } - } - if c[imax] > 1 { - c[imax] -= 1; - } - total -= 1; - } - while total < capacidade_total { - let (mut imax, mut vmax) = (0usize, 0.0f64); - for (i, (&v, &q)) in c.iter().zip(qes.iter()).enumerate() { - let res = q / (v as f64); - if res > vmax { - vmax = res; - imax = i; - } - } - c[imax] += 1; - total += 1; - } - c -} - -// --------------------------------------------------------------------------- -// Aplicação JEPA — codificador/preditor redirecionáveis com MoE -// --------------------------------------------------------------------------- - -pub struct RelatorioJepaMoe { - pub modo: &'static str, - pub perda_pred_inicial: f64, - pub perda_pred_final: f64, - pub perda_vc_final: f64, - pub std_emb: f64, - pub carga_max: usize, - pub descartados: usize, -} - -/// Treino JEPA compacto com três braços (ablação honesta): -/// - `"pequena"`: codificador/preditor MLP simples (baseline); -/// - `"densa"`: MLP com o TOTAL de parâmetros dos especialistas (capacidade -/// equiparada, SEM roteamento — isola o ganho do roteamento); -/// - `"moe"`: `MoeRedirecionavel` nos dois caminhos (Codificador no -/// codificador, Decodificador no preditor) com atenção própria. -pub fn treinar_jepa_com_moe( - patches: &Tensor, - modo: &'static str, - passos: usize, - frac_mascara: f64, - semente: u64, -) -> Result { - let dev = patches.device(); - let n = patches.dim(0)?; - let d = patches.dim(1)?; - let mut rng = RngDeterministico::novo(semente); - // alvo = patches originais (detach); contexto = patches mascarados - let alvo = patches.detach(); - let contexto = { - let mut v = patches.flatten_all()?.to_vec1::()?; - for x in v.iter_mut() { - if rng.uniforme() < frac_mascara { - *x = 0.0; - } - } - Tensor::from_vec(v, (n, d), &dev)? - }; - let e = 4usize; // especialistas por papel no braço moe - let n_top = 2; - let capacidade = n; - - // braços - enum Braco { - Pequena(MlpJepa, MlpJepa), - Densa(MlpJepa, MlpJepa), - Moe(MoeRedirecionavel), - } - let braco = match modo { - "pequena" => Braco::Pequena( - MlpJepa::novo(d, 2 * d, d, &dev)?, - MlpJepa::novo(d, 2 * d, d, &dev)?, - ), - "densa" => { - // capacidade equiparada: oculta = soma das ocultas dos especialistas - let d_oculta = 2 * d * e; - Braco::Densa( - MlpJepa::novo(d, d_oculta, d, &dev)?, - MlpJepa::novo(d, d_oculta, d, &dev)?, - ) - } - _ => Braco::Moe(MoeRedirecionavel::novo(d, 2 * e, n_top, capacidade, &dev)?), - }; - - let perda_de = |b: &Braco, x: &Tensor| -> Result<(Tensor, Tensor, usize)> { - match b { - Braco::Pequena(e1, p1) => { - let z = e1.fwd(x)?; - let yh = p1.fwd(&z)?; - let dif = (&yh - &alvo)?; - Ok(((dif.sqr()?.sum_all()? / (n as f64 * d as f64))?, - z.sqr()?.mean_all()?, 0)) - } - Braco::Densa(e1, p1) => { - let z = e1.fwd(x)?; - let yh = p1.fwd(&z)?; - let dif = (&yh - &alvo)?; - Ok(((dif.sqr()?.sum_all()? / (n as f64 * d as f64))?, - z.sqr()?.mean_all()?, 0)) - } - Braco::Moe(m) => { - // v0.9.0: forward (ex-fwd_vetorizado) — gradiente flui aos - // especialistas e ao roteador (a fwd seccionava o grafo) - let s_enc = m.forward(x, Papel::Codificador)?; - let s_dec = m.forward(&s_enc.saida, Papel::Decodificador)?; - let dif = (&s_dec.saida - &alvo)?; - let lp = (dif.sqr()?.sum_all()? / (n as f64 * d as f64))?; - // perda total = preditiva + aux balanceamento (ambos papéis) - let lt = (lp.affine(1.0, 0.0)? + s_enc.perda_aux.affine(m.lambda_aux, 0.0)? - + s_dec.perda_aux.affine(m.lambda_aux, 0.0)?)?; - let carga_max = s_enc.carga.iter().copied().fold(0, usize::max) - .max(s_dec.carga.iter().copied().fold(0, usize::max)); - Ok((lt, lp, carga_max)) - } - } - }; - - let (mut opt, vars): (AdamWManual, Vec) = match &braco { - Braco::Pequena(a, b) => { - let v = [a.vars(), b.vars()].concat(); - (AdamWManual::novo(v.clone(), 3e-3, 0.0), v) - } - Braco::Densa(a, b) => { - let v = [a.vars(), b.vars()].concat(); - (AdamWManual::novo(v.clone(), 3e-3, 0.0), v) - } - Braco::Moe(m) => (AdamWManual::novo(m.vars(), 3e-3, 0.0), m.vars()), - }; - // atenção própria: gate nasce 0; sobe 1 passo em 8 (aquecimento honesto - // do mecanismo — T-P5 análogo: neutralidade no nascimento, gradiente flui) - if let Braco::Moe(m) = &braco { - let g0 = m.atencao.gate.as_tensor().to_scalar::()? as f64; - let _ = g0; - } - - let (l0, _, _) = perda_de(&braco, &contexto)?; - let perda_inicial = l0.to_scalar::()? as f64; - let mut perda_final = perda_inicial; - let mut vc_final = 0.0; - let mut carga_max = 0usize; - let mut descartados = 0usize; - let mut std_emb = 0.0; - for passo in 0..passos { - let (lt, lp, cm) = perda_de(&braco, &contexto)?; - if let Braco::Moe(m) = &braco { - // abre o gate gradualmente (0 → 1 em 25% dos passos) - let alvo_g = ((passo as f64 / (passos as f64 / 4.0).max(1.0)).min(1.0)) as f32; - let g_atual = m.atencao.gate.as_tensor().to_scalar::()? as f64; - if (alvo_g as f64 - g_atual).abs() > 1e-9 { - m.atencao.gate.set(&Tensor::new(alvo_g, &dev)?)?; - } - } - let grads = crate::treino::grads_achatados(<, &vars)?; - AdamWManual::clipar(&mut grads.clone(), 1.0); - let mut g2 = grads; - AdamWManual::clipar(&mut g2, 5.0); - opt.passo(g2); - perda_final = lp.to_scalar::()? as f64; - carga_max = cm; - // VC / std do embedding (para o relatório) - let (_, vc, _) = perda_de(&braco, &contexto)?; - vc_final = vc.to_scalar::()? as f64; - std_emb = { - let z = match &braco { - Braco::Pequena(a, _) => a.fwd(&contexto)?, - Braco::Densa(a, _) => a.fwd(&contexto)?, - Braco::Moe(m) => m.forward(&contexto, Papel::Codificador)?.saida, - }; - z.flatten_all()?.to_vec1::()?.iter().map(|x| *x as f64).collect::>() - .chunks(n).map(|c| { - let m = c.iter().sum::() / c.len() as f64; - (c.iter().map(|x| (x - m).powi(2)).sum::() / c.len() as f64).sqrt() - }).sum::() / n as f64 - }; - } - if let Braco::Moe(m) = &braco { - let s = m.forward(&contexto, Papel::Codificador)?; - descartados = s.descartados; - } - Ok(RelatorioJepaMoe { - modo, - perda_pred_inicial: perda_inicial, - perda_pred_final: perda_final, - perda_vc_final: vc_final, - std_emb, - carga_max, - descartados, - }) -} - -// --------------------------------------------------------------------------- -// Aplicação SOM — refinamento residual com alocação por QE (M-R5) -// --------------------------------------------------------------------------- - -pub struct RelatorioSomMoe { - pub qe_antes: f64, - pub qe_depois: f64, - pub qe_alocacao_uniforme: f64, - pub carga_por_celula: Vec, - pub tempo_treino_s: f64, - /// qualidade da DECODIFICAÇÃO após redirecionamento (mse w_bmu → x). - pub mse_decodificador: f64, -} - -/// Refina um SOM 4D treinado com um MoE redirecionável: -/// 1. aloca especialistas por célula ∝ QE (water-filling — M-R5); -/// 2. treina o papel Codificador como corretor residual -/// `x̂ = w_bmu(x) + E(x)`; -/// 3. mede QE′; repete com alocação uniforme (ablação do water-filling); -/// 4. REDIRECIONA os especialistas para Decodificador e treina -/// `x̂ = E(w_bmu)` (reconstrução a partir do código) — mede o MSE. -pub fn refinar_som_com_moe( - dados: &Tensor, - centroides: &[Vec], - passos: usize, - semente: u64, -) -> Result { - use std::time::Instant; - let t0 = Instant::now(); - let dev = dados.device(); - let n = dados.dim(0)?; - let d = dados.dim(1)?; - let m = centroides.len(); - // BMU + QE por célula (tolera F32/F64) - let dados_v: Vec> = match dados.to_vec2::() { - Ok(v) => v, - Err(_) => dados - .to_vec2::()? - .iter() - .map(|l| l.iter().map(|x| *x as f64).collect()) - .collect(), - }; - let bmu: Vec = dados_v - .iter() - .map(|x| { - centroides - .iter() - .enumerate() - .min_by(|(i, a), (j, b)| { - let da: f64 = a.iter().zip(x).map(|(w, v)| (w - v).powi(2)).sum(); - let db: f64 = b.iter().zip(x).map(|(w, v)| (w - v).powi(2)).sum(); - da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal).then(i.cmp(j)) - }) - .map(|(i, _)| i) - .unwrap_or(0) - }) - .collect(); - let mut qe_cel = vec![0f64; m]; - let mut cont_cel = vec![0usize; m]; - for (i, b) in bmu.iter().enumerate() { - let dist: f64 = centroides[*b].iter().zip(&dados_v[i]).map(|(w, v)| (w - v).powi(2)).sum(); - qe_cel[*b] += dist.sqrt(); - cont_cel[*b] += 1; - } - for i in 0..m { - if cont_cel[i] > 0 { - qe_cel[i] /= cont_cel[i] as f64; - } - } - let qe_antes: f64 = dados_v - .iter() - .enumerate() - .map(|(i, b)| { - let w = ¢roides[bmu[i]]; - (w.iter().zip(b).map(|(a, v)| (a - v).powi(2)).sum::()).sqrt() - }) - .sum::() - / n as f64; - - // ---------- braço water-filling ---------- - // capacidade = m células + 8 especialistas EXTRA distribuídos ∝ QE - // (com C < m o water-filling degenera em uniforme — mínimo 1 por célula) - let capacidade_total = m + 8; - let aloca_wf = alocar_por_qe(&qe_cel, capacidade_total); - let qe_wf = treinar_corretor(dados, centroides, &bmu, &aloca_wf, passos, semente, &dev)?; - // ---------- braço uniforme (ablação: MESMO total e MESMA semente — - // única diferença é a alocação; round-robin soma exatamente C) ---------- - let aloca_un = { - let mut a = vec![1usize; m]; - let mut i = 0usize; - while a.iter().sum::() < capacidade_total { - a[i % m] += 1; - i += 1; - } - a - }; - let qe_un = treinar_corretor(dados, centroides, &bmu, &aloca_un, passos, semente, &dev)?; - - // ---------- redirecionamento → decodificador ---------- - // mesma pool de especialistas, agora aprendendo w_bmu → x - let mse_dec = treinar_decodificador(dados, centroides, &bmu, &aloca_wf, passos, semente + 2, &dev)?; - - Ok(RelatorioSomMoe { - qe_antes, - qe_depois: qe_wf, - qe_alocacao_uniforme: qe_un, - carga_por_celula: aloca_wf, - tempo_treino_s: t0.elapsed().as_secs_f64(), - mse_decodificador: mse_dec, - }) -} - -/// Corretor residual: x̂ = w_bmu + E_{célula}(x). Treina com AdamW. -fn treinar_corretor( - dados: &Tensor, - centroides: &[Vec], - bmu: &[usize], - alocacao: &[usize], - passos: usize, - semente: u64, - dev: &Device, -) -> Result { - let n = dados.dim(0)?; - let d = dados.dim(1)?; - // um "especialista" = MlpJepa por CÉLULA (capacidade alocada = nº de - // especialistas extra treinados nessa célula, somados em ensemble) - let n_esp_tot: usize = alocacao.iter().sum(); - let mut ensembles: Vec> = Vec::with_capacity(centroides.len()); - for c in alocacao { - let mut v = Vec::new(); - for _ in 0..(*c).max(1) { - v.push(MlpJepa::novo(d, 4 * d, d, dev)?); - } - ensembles.push(v); - } - let mut vars: Vec = Vec::new(); - for ens in &ensembles { - for e in ens { - vars.extend(e.vars()); - } - } - let mut opt = AdamWManual::novo(vars.clone(), 3e-3, 0.0); - // tensores de apoio - let w_bmu = { - let v: Vec = (0..n).flat_map(|i| centroides[bmu[i]].iter().map(|x| *x as f32).collect::>()).collect(); - Tensor::from_vec(v, (n, d), dev)? - }; - let alvo_res = (dados - &w_bmu)?; - for _ in 0..passos { - let mut soma = Tensor::zeros((), candle_core::DType::F32, dev)?; - for (i, ens) in ensembles.iter().enumerate() { - // tokens da célula i - let idx: Vec = (0..n).filter(|&k| bmu[k] == i).map(|k| k as u32).collect(); - if idx.is_empty() { - continue; - } - let m_idx = idx.len(); - let it = Tensor::from_vec(idx, (m_idx,), dev)?; - let x_c = dados.index_select(&it, 0)?; - let r_c = alvo_res.index_select(&it, 0)?; - // ensemble: média das saídas - let mut y = Tensor::zeros((m_idx, d), candle_core::DType::F32, dev)?; - for e in ens { - y = (y.affine(1.0, 0.0)? + e.fwd(&x_c)?.affine(1.0, 0.0)?)?; - } - let k = ens.len() as f64; - let yh = y.affine(1.0 / k, 0.0)?; - let dif = (&yh - &r_c)?; - soma = (soma.affine(1.0, 0.0)? + dif.sqr()?.sum_all()?.affine(1.0 / (n as f64 * d as f64), 0.0)?)?; - } - let _ = n_esp_tot; - let grads = crate::treino::grads_achatados(&soma, &vars)?; - let mut g = grads; - AdamWManual::clipar(&mut g, 5.0); - opt.passo(g); - } - // QE refinado - let mut qe = 0f64; - for (i, ens) in ensembles.iter().enumerate() { - let idx: Vec = (0..n).filter(|&k| bmu[k] == i).map(|k| k as u32).collect(); - if idx.is_empty() { - continue; - } - let m_idx = idx.len(); - let it = Tensor::from_vec(idx, (m_idx,), dev)?; - let x_c = dados.index_select(&it, 0)?; - let mut y = Tensor::zeros((m_idx, d), candle_core::DType::F32, dev)?; - for e in ens { - y = (y.affine(1.0, 0.0)? + e.fwd(&x_c)?.affine(1.0, 0.0)?)?; - } - let k = ens.len() as f64; - let yh = y.affine(1.0 / k, 0.0)?; - let xhat = (w_bmu.index_select(&it, 0)? + &yh)?; - let dif = (&x_c - &xhat)?; - qe += dif.sqr()?.sum_all()?.to_scalar::()? as f64; - } - Ok((qe / (n as f64 * d as f64)).sqrt() * (d as f64).sqrt()) // QE médio por ponto (norma L2) -} - -/// Decodificador (pós-redirecionamento): x̂ = E(w_bmu) — reconstrução -/// a partir apenas do código (centróide). Mede o MSE final. -fn treinar_decodificador( - dados: &Tensor, - centroides: &[Vec], - bmu: &[usize], - alocacao: &[usize], - passos: usize, - semente: u64, - dev: &Device, -) -> Result { - let _ = semente; - let n = dados.dim(0)?; - let d = dados.dim(1)?; - let mut ensembles: Vec> = Vec::with_capacity(centroides.len()); - for c in alocacao { - let mut v = Vec::new(); - for _ in 0..(*c).max(1) { - v.push(MlpJepa::novo(d, 4 * d, d, dev)?); - } - ensembles.push(v); - } - let mut vars: Vec = Vec::new(); - for ens in &ensembles { - for e in ens { - vars.extend(e.vars()); - } - } - let mut opt = AdamWManual::novo(vars.clone(), 3e-3, 0.0); - let w_bmu = { - let v: Vec = (0..n).flat_map(|i| centroides[bmu[i]].iter().map(|x| *x as f32).collect::>()).collect(); - Tensor::from_vec(v, (n, d), dev)? - }; - for _ in 0..passos { - let mut soma = Tensor::zeros((), candle_core::DType::F32, dev)?; - for (i, ens) in ensembles.iter().enumerate() { - let idx: Vec = (0..n).filter(|&k| bmu[k] == i).map(|k| k as u32).collect(); - if idx.is_empty() { - continue; - } - let m_idx = idx.len(); - let it = Tensor::from_vec(idx, (m_idx,), dev)?; - let w_c = w_bmu.index_select(&it, 0)?; - let x_c = dados.index_select(&it, 0)?; - let mut y = Tensor::zeros((m_idx, d), candle_core::DType::F32, dev)?; - for e in ens { - y = (y.affine(1.0, 0.0)? + e.fwd(&w_c)?.affine(1.0, 0.0)?)?; - } - let k = ens.len() as f64; - let yh = y.affine(1.0 / k, 0.0)?; - let dif = (&yh - &x_c)?; - soma = (soma.affine(1.0, 0.0)? + dif.sqr()?.sum_all()?.affine(1.0 / (n as f64 * d as f64), 0.0)?)?; - } - let grads = crate::treino::grads_achatados(&soma, &vars)?; - let mut g = grads; - AdamWManual::clipar(&mut g, 5.0); - opt.passo(g); - } - // MSE final do decodificador - let mut mse = 0f64; - for (i, ens) in ensembles.iter().enumerate() { - let idx: Vec = (0..n).filter(|&k| bmu[k] == i).map(|k| k as u32).collect(); - if idx.is_empty() { - continue; - } - let m_idx = idx.len(); - let it = Tensor::from_vec(idx, (m_idx,), dev)?; - let w_c = w_bmu.index_select(&it, 0)?; - let x_c = dados.index_select(&it, 0)?; - let mut y = Tensor::zeros((m_idx, d), candle_core::DType::F32, dev)?; - for e in ens { - y = (y.affine(1.0, 0.0)? + e.fwd(&w_c)?.affine(1.0, 0.0)?)?; - } - let k = ens.len() as f64; - let yh = y.affine(1.0 / k, 0.0)?; - let dif = (&yh - &x_c)?; - mse += dif.sqr()?.sum_all()?.to_scalar::()? as f64; - } - Ok(mse / (n as f64 * d as f64)) -} - -pub fn versao() -> &'static str { - "moe-redirecionavel-0.6.0" -} - -#[cfg(test)] -#[allow(deprecated)] // testes usam o oráculo fwd (bug v080-1) de propósito -mod testes { - use super::*; - - fn dados_sinteticos(n: usize, d: usize, semente: u64) -> Tensor { - let mut rng = crate::rng::RngDeterministico::novo(semente); - let v: Vec = (0..n * d).map(|_| rng.normal(0.0, 1.0) as f32).collect(); - Tensor::from_vec(v, (n, d), &Device::Cpu).unwrap() - } - - #[test] - fn mr1_invariancia_sob_redirecionamento() { - let dev = Device::Cpu; - let x = dados_sinteticos(64, 16, 3); - let mut moe = MoeRedirecionavel::novo(16, 6, 2, 64, &dev).unwrap(); - let s1 = moe.fwd(&x, Papel::Codificador).unwrap(); - let perda_antes = s1.perda_aux.to_scalar::().unwrap() as f64; - let carga_antes: usize = s1.carga.iter().sum(); - moe.redirecionar_todos(); - // após trocar TODOS os papéis, a passada Decodificador usa os - // especialistas que eram do codificador — a perda auxiliar do - // SUBCONJUNTO é comparável (mesma distribuição de roteamento) - let s2 = moe.fwd(&x, Papel::Decodificador).unwrap(); - let perda_depois = s2.perda_aux.to_scalar::().unwrap() as f64; - let carga_depois: usize = s2.carga.iter().sum(); - assert_eq!(carga_antes, carga_depois, "capacidade total conservada (M-R1)"); - assert!( - (perda_antes - perda_depois).abs() < 2.0 + 1e-6, - "perda auxiliar da família conservada: {perda_antes:.4} vs {perda_depois:.4}" - ); - } - - #[test] - fn mr2_neutralidade_no_nascimento() { - let dev = Device::Cpu; - let x = dados_sinteticos(32, 8, 5); - let moe = MoeRedirecionavel::novo(8, 4, 2, 32, &dev).unwrap(); - let g = moe.atencao.gate.as_tensor().to_scalar::().unwrap(); - assert!(g == 0.0, "gate nasce zero"); - let y = moe.atencao.fwd(&x).unwrap(); - let dif = (&y - &x).unwrap().sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap(); - assert!(dif == 0.0, "y = x bit-a-bit no nascimento (dif={dif})"); - } - - #[test] - fn mr3_paralelismo_e_determinismo() { - let dev = Device::Cpu; - let x = dados_sinteticos(96, 24, 7); - let moe = MoeRedirecionavel::novo(24, 8, 2, 96, &dev).unwrap(); - let (_enc, _dec, t_par, t_seq, speedup) = moe.fwd_ambos_par(&x).unwrap(); - // determinismo já garantido por assert interno; speedup registrado - assert!(t_par > 0.0 && t_seq > 0.0); - let _ = speedup; - } - - #[test] - fn mr5_water_filling_minimiza_cota() { - // cota Σ q_i²/c_i: water-filling ≤ uniforme (Cauchy–Schwarz) - let qes = vec![0.2, 0.5, 1.0, 0.3]; - let c_wf = alocar_por_qe(&qes, 8); - let c_un = vec![2usize; 4]; - let f = |c: &[usize]| qes.iter().zip(c).map(|(q, ci)| q * q / *ci as f64).sum::(); - assert!( - f(&c_wf) <= f(&c_un) + 1e-9, - "water-filling {c_wf:?} (cota {:.4}) deve ≤ uniforme (cota {:.4})", - f(&c_wf), f(&c_un) - ); - assert_eq!(c_wf.iter().sum::(), 8); - } - - #[test] - fn jepa_moe_aprende() { - let x = dados_sinteticos(64, 12, 11); - let r = treinar_jepa_com_moe(&x, "moe", 60, 0.5, 42).unwrap(); - assert!(r.perda_pred_final < r.perda_pred_inicial, "perda preditiva cai"); - } - - /// **M-R6 (v0.8.0):** equivalência de VALORES entre `fwd` e - /// `forward` — mesma seleção (auxiliar compartilhado), mesma - /// saída, mesma perda auxiliar, mesma carga/descartados. - #[test] - fn mr6_equivalencia_de_valores() { - let dev = Device::Cpu; - for (n, d, e, k, cap, seed) in [ - (64usize, 16usize, 6usize, 2usize, 64usize, 3u64), - (96, 24, 8, 2, 96, 5), - (48, 12, 4, 1, 24, 9), // capacidade apertada: força descartados - ] { - let x = dados_sinteticos(n, d, seed); - let moe = MoeRedirecionavel::novo(d, e, k, cap, &dev).unwrap(); - let s_ref = moe.fwd(&x, Papel::Codificador).unwrap(); - let s_vet = moe.forward(&x, Papel::Codificador).unwrap(); - assert_eq!(s_ref.carga, s_vet.carga, "M-R6: mesma carga (n={n})"); - assert_eq!(s_ref.usados, s_vet.usados, "M-R6: mesmos usados"); - assert_eq!(s_ref.descartados, s_vet.descartados, "M-R6: mesmos descartados"); - let dif = (&s_ref.saida - &s_vet.saida).unwrap() - .abs().unwrap().max_all().unwrap().to_scalar::().unwrap(); - assert!(dif < 1e-4, "M-R6: max|Δsaída| = {dif} (n={n}, cap={cap})"); - let pa = s_ref.perda_aux.to_scalar::().unwrap(); - let pb = s_vet.perda_aux.to_scalar::().unwrap(); - assert!( - (pa - pb).abs() < 1e-4, - "M-R6: perda auxiliar {pa:.6} vs {pb:.6} (n={n})" - ); - } - } - - /// **M-R7 (v0.8.0):** restauração do fluxo de gradiente — bug v080-1. - /// Com a `fwd` (referência de valores) os especialistas e o roteador - /// NÃO recebem gradiente (grafo seccionado por to_vec2/from_vec); com - /// a `forward` TODOS recebem (index_add/index_select vivos). - #[test] - fn mr7_restauracao_do_gradiente() { - let dev = Device::Cpu; - let x = dados_sinteticos(48, 12, 13); - let moe = MoeRedirecionavel::novo(12, 6, 2, 48, &dev).unwrap(); - let norma_grads = |vetorizada: bool| -> (f64, f64) { - let s = if vetorizada { - moe.forward(&x, Papel::Codificador).unwrap() - } else { - moe.fwd(&x, Papel::Codificador).unwrap() - }; - let perda = s.saida.sqr().unwrap().sum_all().unwrap(); - let grads = perda.backward().unwrap(); - let mut n_roteador = 0.0f64; - for v in moe.roteador.vars() { - if let Some(g) = grads.get(&v) { - n_roteador += g.sqr().unwrap().sum_all().unwrap() - .to_scalar::().unwrap() as f64; - } - } - let mut n_experts = 0.0f64; - for e in &moe.especialistas { - for v in e.vars() { - if let Some(g) = grads.get(&v) { - n_experts += g.sqr().unwrap().sum_all().unwrap() - .to_scalar::().unwrap() as f64; - } - } - } - (n_roteador, n_experts) - }; - // (a) o bug v080-1 é REAL: na fwd, roteador e especialistas têm - // gradiente ZERO (grafo seccionado) — documentado, não corrigido na - // fwd (referência de valores preservada por decisão de projeto) - let (r_ref, e_ref) = norma_grads(false); - assert!( - r_ref == 0.0 && e_ref == 0.0, - "M-R7: bug v080-1 documentado — fwd secciona o grafo (roteador {r_ref}, experts {e_ref})" - ); - // (b) a correção: na vetorizada, TODOS recebem gradiente - let (r_vet, e_vet) = norma_grads(true); - assert!( - r_vet > 0.0, - "M-R7: roteador recebe gradiente na vetorizada ({r_vet})" - ); - assert!( - e_vet > 0.0, - "M-R7: especialistas recebem gradiente na vetorizada ({e_vet})" - ); - } - - /// **M-R8 (v0.8.0):** fim-a-fim — com a vetorizada, o treino REALMENTE - /// atualiza os pesos dos especialistas (regressão do bug v080-1: antes - /// só a atenção própria treinava no braço MoE). - #[test] - fn mr8_treino_atualiza_especialistas() { - let dev = Device::Cpu; - let x = dados_sinteticos(48, 12, 17); - let moe = MoeRedirecionavel::novo(12, 6, 2, 48, &dev).unwrap(); - // snapshot com dados PRÓPRIOS (to_vec1) — detach() compartilha o - // storage e o set() do otimizador sobrescreveria a "cópia" - let snapshot: Vec> = moe - .especialistas - .iter() - .map(|e| { - e.vars()[0].as_tensor().flatten_all().unwrap().to_vec1::().unwrap() - }) - .collect(); - // treino curto com gradiente fluindo - let vars = moe.vars(); - let mut opt = crate::treino::AdamWManual::novo(vars.clone(), 3e-3, 0.0); - for _ in 0..5 { - let s = moe.forward(&x, Papel::Codificador).unwrap(); - let perda = s.saida.sqr().unwrap().sum_all().unwrap(); - let grads = crate::treino::grads_achatados(&perda, &vars).unwrap(); - opt.passo(grads); - } - let mut delta_max = 0.0f64; - for (e, antes) in moe.especialistas.iter().zip(snapshot.iter()) { - let agora = e.vars()[0].as_tensor().flatten_all().unwrap() - .to_vec1::().unwrap(); - let dif = agora.iter().zip(antes.iter()).map(|(a, b)| (a - b).abs()).fold(0f32, f32::max); - delta_max = delta_max.max(dif as f64); - } - let _ = &vars; - assert!( - delta_max > 0.0, - "M-R8: especialistas devem mudar no treino (delta_max={delta_max})" - ); - } -} - -#[cfg(test)] -mod teste_som_moe_integrado { - use super::*; - - #[test] - fn debug_som_com_moe_caminho() { - let dev = Device::Cpu; - let dados_v: Vec> = (0..32).map(|i| (0..16).map(|j| (i * 7 + j * 3) as f64 / 100.0).collect()).collect(); - let (n, d) = (dados_v.len(), 16); - let t = Tensor::from_vec(dados_v.into_iter().flatten().collect::>(), (n, d), &dev).unwrap() - .to_dtype(candle_core::DType::F32).unwrap(); - let cfg = crate::som4d::CfgSom4D { nx: 2, ny: 2, nz: 2, nt: 2, ..Default::default() }; - let som = crate::som4d::treinar_som4d(&t, &cfg, &dev).unwrap(); - let centroides = som.prototipos.clone(); - let r = refinar_som_com_moe(&t, ¢roides, 30, 1).unwrap(); - println!("qe {} -> {} (wf) | uniforme {}", r.qe_antes, r.qe_depois, r.qe_alocacao_uniforme); - assert!(r.qe_depois < r.qe_antes, "corretor MoE reduz o QE"); - assert!(r.mse_decodificador < r.qe_antes.powi(2), "redirect decodifica"); - } -} diff --git a/src/moe_redirecionavel/atencao.rs b/src/moe_redirecionavel/atencao.rs new file mode 100644 index 0000000000000000000000000000000000000000..214789d1ad4fe32c333f85a79438c895bff94a42 --- /dev/null +++ b/src/moe_redirecionavel/atencao.rs @@ -0,0 +1,63 @@ +//! `moe_redirecionavel::atencao` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +// --------------------------------------------------------------------------- +// Atenção própria (independente e paralela à ADBA do tronco) +// --------------------------------------------------------------------------- + +pub struct AtencaoPropria { + pub q: Linear, + pub k: Linear, + pub v: Linear, + pub o: Linear, + /// gate escalar — nasce ZERO (M-R2: neutralidade no nascimento). + pub gate: Var, + pub n_cabecas: usize, +} + +impl AtencaoPropria { + pub fn novo(d: usize, n_cabecas: usize, dev: &Device) -> Result { + let desvio = 1.0 / ((d * 2) as f64).sqrt(); + let mk = || Linear::sem_vies(d, d, desvio, dev); + let gate = Tensor::zeros((), candle_core::DType::F32, dev)?; + Ok(AtencaoPropria { + q: mk()?, k: mk()?, v: mk()?, o: mk()?, + gate: Var::from_tensor(&gate)?, + n_cabecas, + }) + } + + /// x (n, d) → y = x + g·attn(x) — multi-cabeça compacta (sem máscara: + /// atenção plena entre os tokens da entrada do MoE). + pub fn fwd(&self, x: &Tensor) -> Result { + let d = x.dim(candle_core::D::Minus1)?; + let d_cab = d / self.n_cabecas; + let n = x.dim(0)?; + let forma = (self.n_cabecas, n, d_cab); + let q = self.q.fwd(x)?.reshape(forma)?; + let k = self.k.fwd(x)?.reshape(forma)?; + let v = self.v.fwd(x)?.reshape(forma)?; + let scores = q.matmul(&k.transpose(1, 2)?)?.affine(1.0 / (d_cab as f64).sqrt(), 0.0)?; + let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1)?; + let ctx = attn.matmul(&v)?; // (h, n, d_cab) + let ctx = ctx.reshape((n, d))?; + let saida = self.o.fwd(&ctx)?; + // y = x + g·saida (g=0 no nascimento ⇒ y = x — M-R2) + let g = self.gate.as_tensor(); + let forma: Vec = saida.dims().to_vec(); + let delta = saida.broadcast_mul(&g.broadcast_as(forma)?)?; + Ok((x + &delta)?) + } + + pub fn vars(&self) -> Vec { + let mut v = self.q.vars(); + v.extend(self.k.vars()); + v.extend(self.v.vars()); + v.extend(self.o.vars()); + v.push(self.gate.clone()); + v + } +} diff --git a/src/moe_redirecionavel/mod.rs b/src/moe_redirecionavel/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..b5e9e63716dbc245e579f118fadb0dea26a98b36 --- /dev/null +++ b/src/moe_redirecionavel/mod.rs @@ -0,0 +1,57 @@ +//! MoE redirecionável — módulos: atenção própria, núcleo, +//! treinos JEPA/SOM (divisão SRP v0.10.0 — r-35 §2). + +//! **MoEs de codificação/decodificação REDIRECIONÁVEIS com Mecanismo de +//! Atenção próprio e independente (paralelo)** (v0.6.0 — pedido do usuário). +//! +//! Cada `MoeRedirecionavel` possui: +//! - um roteador aprendido `d → E`; +//! - E especialistas `MlpJepa`, cada um com um PAPEL (Codificador ou +//! Decodificador) — o conjunto de papéis é REDIRECIONÁVEL em tempo de +//! execução (permutação), sem reconstrução do modelo; +//! - uma ATENÇÃO PRÓPRIA independente da atenção ADBA do tronco, com gate +//! escalar nascido em ZERO (neutralidade no nascimento); +//! - capacidade por especialista e perda auxiliar de balanceamento. +//! +//! **Provas completas em `docs/memorias/r-25-*`; síntese:** +//! - **M-R1 (invariância sob redirecionamento):** o redirecionamento é uma +//! permutação π dos papéis; o multiconjunto de atribuições +//! (especialista, token) numa passada por papel é levado ao multiconjunto +//! da passada π(papel) — a capacidade total e a perda auxiliar de +//! balanceamento E·Σ f_e·p_e são invariantes (soma sobre todos os e). +//! - **M-R2 (neutralidade da atenção própria no nascimento):** com +//! `g = 0`, `y = x` bit-a-bit; a perturbação é limitada por +//! `‖Δy‖ ≤ L_soft·|g|·‖W_o·V‖` (Lipschitz do softmax com constante +//! L_soft = 1 em probabilidade; estrutura idêntica ao T-P5). +//! - **M-R3 (paralelismo dos dois caminhos):** numa mesma entrada, as +//! passadas Codificador e Decodificador tocam conjuntos DISJUNTOS de +//! especialistas e compartilham apenas leitura — o DAG de computação tem +//! duas sub-DAGs independentes ⇒ tempo paralelo (2 processadores) = +//! max(T_enc, T_dec) + sobrecarga < T_enc + T_dec = tempo sequencial +//! (modelo PRAM; medido empiricamente com threads). +//! - **M-R4 (fechamento da classe de funções):** redirecionar equivale a +//! permutar colunas do roteador e re-rotular papéis; a classe de funções +//! implementáveis é fechada sob π ⇒ expressividade inalterada. +//! - **M-R5 (water-filling por QE — Cauchy–Schwarz):** dadas capacidades +//! totais C e erros por célula q_i, a alocação `c_i ∝ q_i` minimiza +//! `Σ q_i²/c_i ≥ (Σq_i)²/C`, cota superior do erro refinado ponderado. + +use candle_core::{Device, Result, Tensor, Var}; +use crate::camadas::Linear; +use crate::jepa::MlpJepa; +use crate::rng::RngDeterministico; +use crate::treino::AdamWManual; + +// ---- divisão SRP v0.10.0 (r-35 §2): submódulos + re-exports ---- +#[cfg(test)] +mod testes; +#[cfg(test)] +mod teste_integrado; +mod atencao; +mod nucleo; +mod treino; + +pub use atencao::*; +pub use nucleo::*; +pub use treino::*; + diff --git a/src/moe_redirecionavel/nucleo.rs b/src/moe_redirecionavel/nucleo.rs new file mode 100644 index 0000000000000000000000000000000000000000..f898be5e9b758a3170fcaa6b8976a137afd192f4 --- /dev/null +++ b/src/moe_redirecionavel/nucleo.rs @@ -0,0 +1,431 @@ +//! `moe_redirecionavel::nucleo` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +// --------------------------------------------------------------------------- +// MoE redirecionável +// --------------------------------------------------------------------------- + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Papel { + Codificador, + Decodificador, +} + +impl Papel { + pub fn nome(&self) -> &'static str { + match self { Papel::Codificador => "codificador", Papel::Decodificador => "decodificador" } + } +} + +pub struct SaidaMoeR { + pub saida: Tensor, + pub perda_aux: Tensor, + pub carga: Vec, + pub descartados: usize, + /// especialistas efetivamente usados nesta passada (índices globais). + pub usados: Vec, +} + +pub struct MoeRedirecionavel { + pub roteador: Linear, // d → E + pub especialistas: Vec, // E experts d → d + pub papeis: Vec, // papel corrente de cada especialista + pub atencao: AtencaoPropria, // atenção PRÓPRIA (M-R2/M-R3) + pub n_top: usize, + pub capacidade: usize, + pub lambda_aux: f64, +} + +impl MoeRedirecionavel { + pub fn novo(d: usize, n_expertos: usize, n_top: usize, capacidade: usize, dev: &Device) -> Result { + let roteador = Linear::sem_vies(d, n_expertos, 0.1, dev)?; + let especialistas = (0..n_expertos) + .map(|_| MlpJepa::novo(d, 2 * d, d, dev)) + .collect::>>()?; + // papéis iniciais alternados (metade cod, metade dec) + let papeis = (0..n_expertos) + .map(|i| if i % 2 == 0 { Papel::Codificador } else { Papel::Decodificador }) + .collect(); + Ok(MoeRedirecionavel { + roteador, + especialistas, + papeis, + atencao: AtencaoPropria::novo(d, 2, dev)?, + n_top: n_top.max(1), + capacidade, + lambda_aux: 0.01, + }) + } + + pub fn vars(&self) -> Vec { + let mut v = self.roteador.vars(); + for e in &self.especialistas { + v.extend(e.vars()); + } + v.extend(self.atencao.vars()); + v + } + + /// **M-R1/M-R4:** redireciona os papéis segundo a permutação `permuta` + /// (novo_papel[i] = papel_anterior[permuta[i]]). `redirecionar_todos` + /// troca Codificador ↔ Decodificador. + pub fn redirecionar(&mut self, permuta: &[usize]) { + let antigos = self.papeis.clone(); + for (i, &p) in permuta.iter().enumerate() { + if p < antigos.len() { + self.papeis[i] = antigos[p]; + } + } + } + + pub fn redirecionar_todos(&mut self) { + for p in self.papeis.iter_mut() { + *p = match *p { + Papel::Codificador => Papel::Decodificador, + Papel::Decodificador => Papel::Codificador, + }; + } + } + + fn indices_do_papel(&self, papel: Papel) -> Vec { + self.papeis.iter().enumerate().filter(|(_, p)| **p == papel).map(|(i, _)| i).collect() + } + + /// Passada por papel: atenção própria → roteamento top-k DENTRO do + /// subconjunto de especialistas do papel → capacidade → soma ponderada. + /// + /// ⚠️ **DEPRECIADA na v0.9.0 (crítica Qwen3.8 nº 2b / r-34 §4):** + /// **bug v080-1 catalogado (r-33 §5)** — esta versão materializa os + /// valores com `to_vec2`/`from_vec` no acúmulo, o que SECCIONA o grafo + /// de autograd: especialistas e roteador NÃO recebem gradiente por + /// esta saída (só a atenção própria treina). Fora da superfície + /// pública (`pub(crate)`) desde a v0.9.0 — mantida EXCLUSIVAMENTE como + /// oráculo dos testes M-R6 (equivalência de valores) e M-R7 (prova do + /// bug); o caminho de treino canônico é [`MoeRedirecionavel::forward`]. + #[deprecated(since = "0.9.0", note = "bug v080-1: secciona o grafo de autograd — use forward()")] + pub(crate) fn fwd(&self, x: &Tensor, papel: Papel) -> Result { + let y = self.atencao.fwd(x)?; // (n, d) — atenção própria independente + let n = y.dim(0)?; + let d = y.dim(1)?; + let dev = y.device(); + let do_papel = self.indices_do_papel(papel); + let e_local = do_papel.len(); + let mut descartados = 0usize; + let mut saida = Tensor::zeros((n, d), candle_core::DType::F32, &dev)?; + let mut peso_total = Tensor::zeros((n, 1), candle_core::DType::F32, &dev)?; + // probs de roteamento só sobre especialistas do papel (mascarado) + let logits_todos = self.roteador.fwd(&y)?; // (n, E) + let mut probs_v: Vec> = logits_todos.to_vec2::()?; + for linha in probs_v.iter_mut() { + // máscara: -inf fora do papel + for (e, lv) in linha.iter_mut().enumerate() { + if !do_papel.contains(&e) { + *lv = f32::NEG_INFINITY; + } + } + } + // softmax manual por linha (com -inf → 0) + let probs: Vec> = probs_v + .iter() + .map(|linha| { + let m = linha.iter().copied().fold(f32::NEG_INFINITY, f32::max); + let exps: Vec = linha.iter().map(|v| (v - m).exp()).collect(); + let s: f32 = exps.iter().sum(); + if s <= 0.0 { + vec![0.0; linha.len()] + } else { + exps.iter().map(|e| e / s).collect() + } + }) + .collect(); + // seleção top-k com capacidade (código extraído — MESMA semântica + // de desempate da v0.6.0, compartilhada com forward) + let (designacao, carga) = selecionar_com_capacidade( + &probs, + &do_papel, + n, + self.n_top, + self.capacidade, + self.especialistas.len(), + ); + let usados: Vec = designacao.iter().map(|(ei, _)| *ei).collect(); + for (ei, escolhidos) in designacao { + let idx: Vec = escolhidos.iter().map(|&i| i as u32).collect(); + let n_e = idx.len(); + let idx_t = Tensor::from_vec(idx, (n_e,), &dev)?; + let tile = y.index_select(&idx_t, 0)?; + let y_e = self.especialistas[ei].fwd(&tile)?; + let w: Vec = escolhidos.iter().map(|&i| probs[i][ei]).collect(); + let w_t = Tensor::from_vec(w.clone(), (n_e, 1), &dev)?; + let contrib = y_e.broadcast_mul(&w_t)?; + let mut buf = saida.to_vec2::()?; + let contrib_v = contrib.to_vec2::()?; + for (kk, &i) in escolhidos.iter().enumerate() { + for j in 0..d { + buf[i][j] += contrib_v[kk][j]; + } + } + saida = Tensor::from_vec(buf.into_iter().flatten().collect::>(), (n, d), &dev)?; + let mut pt = peso_total.to_vec2::()?; + for (kk, &i) in escolhidos.iter().enumerate() { + pt[i][0] += w[kk]; + } + peso_total = Tensor::from_vec(pt.into_iter().flatten().collect::>(), (n, 1), &dev)?; + } + let pt_v = peso_total.to_vec2::()?; + for i in 0..n { + if pt_v[i][0] == 0.0 { + descartados += 1; + } + } + let denom = peso_total.clamp(1e-6, f64::INFINITY)?; + let saida = saida.broadcast_div(&denom)?; + // saída residual: y + roteamento (atenção já embutida em y) + let saida = (&y + &saida)?; + // perda auxiliar sobre o SUBCONJUNTO do papel (invariante global — M-R1) + let mut f = vec![0f32; e_local.max(1)]; + let mut m = 0usize; + for i in 0..n { + let (mut melhor, mut mp) = (0usize, f32::NEG_INFINITY); + for (li, &g) in do_papel.iter().enumerate() { + if probs[i][g] > mp { + mp = probs[i][g]; + melhor = li; + } + } + f[melhor] += 1.0; + m += 1; + } + if m > 0 && e_local > 0 { + for v in f.iter_mut() { + *v /= m as f32; + } + } + let f_t = Tensor::from_vec(f, (e_local.max(1),), &dev)?; + let p_media_local: Vec = (0..e_local) + .map(|li| { + let g = do_papel[li]; + (0..n).map(|i| probs[i][g]).sum::() / n.max(1) as f32 + }) + .collect(); + let p_t = Tensor::from_vec(p_media_local, (e_local.max(1),), &dev)?; + let perda_aux = ((&f_t * &p_t)?.sum_all()? * (e_local.max(1) as f64))?; + Ok(SaidaMoeR { saida, perda_aux, carga, descartados, usados }) + } + + /// **M-R6 (v0.8.0):** passada VETORIZADA — roteamento, acúmulo e perda + /// auxiliar em OPERAÇÕES DE TENSOR rastreadas (proposta Qwen-3.8 nº 4 + /// IMPLEMENTADA de verdade: o esboço original terminava em + /// `unimplemented!()`; a fórmula da perda auxiliar `E·Σ f_e·p̄_e` + /// estava CORRETA — Switch Transformer — e é mantida). + /// + /// **Caminho canônico desde a v0.9.0 (antes `fwd_vetorizado`):** + /// correção do bug v080-1 — a acumulação usa `index_add` (op + /// rastreada pelo autograd do candle — `IndexAdd` em op.rs) em vez do + /// round-trip `to_vec2`/`from_vec`; os pesos de roteamento saem do + /// PRÓPRIO tensor `probs` (vivo até o roteador); a perda auxiliar usa + /// `p̄` do tensor vivo com `f` constante (padrão Switch: argmax é + /// não-diferenciável, a média de probs carrega o gradiente). O + /// gradiente flui por TODOS os caminhos (teste M-R7) com VALORES + /// idênticos aos da referência `fwd` (mesma seleção — teste M-R6). + pub fn forward(&self, x: &Tensor, papel: Papel) -> Result { + let y = self.atencao.fwd(x)?; // (n, d) + let n = y.dim(0)?; + let d = y.dim(1)?; + let dev = y.device(); + let e_total = self.especialistas.len(); + let do_papel = self.indices_do_papel(papel); + let e_local = do_papel.len(); + // 1) máscara do papel + softmax EM TENSOR — grafo VIVO até o roteador + let mut mascara_v = vec![f32::NEG_INFINITY; e_total]; + for &e in &do_papel { + mascara_v[e] = 0.0; + } + let mascara = Tensor::from_vec(mascara_v, (1, e_total), &dev)?; + let logits = self.roteador.fwd(&y)?; + let mascarados = logits.broadcast_add(&mascara)?; + let probs = candle_nn::ops::softmax(&mascarados, candle_core::D::Minus1)?; // (n, E) VIVO + // 2) seleção discreta (única materialização — o roteamento duro é + // não-diferenciável por natureza; os PESOS ficam vivos no passo 3) + let probs_v = probs.to_vec2::()?; + let (designacao, carga) = + selecionar_com_capacidade(&probs_v, &do_papel, n, self.n_top, self.capacidade, e_total); + let usados: Vec = designacao.iter().map(|(ei, _)| *ei).collect(); + // 3) acúmulo VIVO: index_add é rastreada — especialistas recebem + // gradiente pelas contribuições, e o roteador pelos pesos w + let mut saida = Tensor::zeros((n, d), candle_core::DType::F32, &dev)?; + let mut peso_total = Tensor::zeros((n, 1), candle_core::DType::F32, &dev)?; + for (ei, escolhidos) in &designacao { + let idx: Vec = escolhidos.iter().map(|&i| i as u32).collect(); + let n_e = idx.len(); + let idx_t = Tensor::from_vec(idx, (n_e,), &dev)?; + let tile = y.index_select(&idx_t, 0)?; // VIVO + let y_e = self.especialistas[*ei].fwd(&tile)?; // VIVO (gradiente!) + // w = probs[i][ei] VIVO: gradiente flui ao roteador + let linhas = probs.index_select(&idx_t, 0)?; // (n_e, E) VIVO + let w_t = linhas.narrow(1, *ei, 1)?.contiguous()?; // (n_e, 1) VIVO + let contrib = y_e.broadcast_mul(&w_t)?; // (n_e, d) VIVO + saida = saida.index_add(&idx_t, &contrib, 0)?; // VIVO (M-R6) + peso_total = peso_total.index_add(&idx_t, &w_t, 0)?; // VIVO + } + // 4) normalização idêntica à fwd (clamp 1e-6 nos descartados) + let pt_v = peso_total.to_vec2::()?; // estatística apenas + let mut descartados = 0usize; + for i in 0..n { + if pt_v[i][0] == 0.0 { + descartados += 1; + } + } + let denom = peso_total.clamp(1e-6, f64::INFINITY)?; + let saida = saida.broadcast_div(&denom)?; + let saida = (&y + &saida)?; // residual — como a fwd + // 5) perda auxiliar EM TENSOR (fórmula M-R1/Switch: e_local·Σ f_e·p̄_e) + let cols: Vec = do_papel.iter().map(|&e| e as u32).collect(); + let cols_t = Tensor::from_vec(cols, (e_local.max(1),), &dev)?; + let probs_local = probs.index_select(&cols_t, 1)?; // (n, e_local) VIVO + let argmax_v = probs_local.argmax(candle_core::D::Minus1)?.to_vec1::()?; + let mut f = vec![0f32; e_local.max(1)]; + for &a in &argmax_v { + f[a as usize] += 1.0; + } + if n > 0 { + for v in f.iter_mut() { + *v /= n as f32; + } + } + let f_t = Tensor::from_vec(f, (e_local.max(1),), &dev)?; + let p_barra = probs_local.mean_keepdim(0)?.flatten_all()?; // (e_local,) VIVO + let perda_aux = ((&f_t * &p_barra)?.sum_all()? * (e_local.max(1) as f64))?; + Ok(SaidaMoeR { saida, perda_aux, carga, descartados, usados }) + } + + /// **M-R3:** passadas Codificador e Decodificador em DUAS THREADS + /// (DAGs independentes — especialistas disjuntos por papel). Devolve + /// (enc, dec, tempo_paralelo_s, tempo_sequencial_s, speedup). + pub fn fwd_ambos_par(&self, x: &Tensor) -> Result<(SaidaMoeR, SaidaMoeR, f64, f64, f64)> { + use std::time::Instant; + // sequencial (medição) + let t0 = Instant::now(); + let enc_seq = self.forward(x, Papel::Codificador)?; + let dec_seq = self.forward(x, Papel::Decodificador)?; + let t_seq = t0.elapsed().as_secs_f64(); + // paralelo (2 threads com escopo — referências compartilhadas) + let t0 = Instant::now(); + let (enc, dec) = { + let xref = &x; + let sref = &self; + std::thread::scope(|sc| { + let h1 = sc.spawn(move || sref.forward(xref, Papel::Codificador)); + let h2 = sc.spawn(move || sref.forward(xref, Papel::Decodificador)); + let r1 = h1.join().unwrap_or_else(|_| Err(candle_core::Error::Msg("thread enc".into()))); + let r2 = h2.join().unwrap_or_else(|_| Err(candle_core::Error::Msg("thread dec".into()))); + (r1, r2) + }) + }; + let (enc, dec) = (enc?, dec?); + let t_par = t0.elapsed().as_secs_f64(); + // determinismo: paralelo == sequencial (mesmas entradas, especialistas disjuntos) + let dif = (&enc.saida - &enc_seq.saida)?.sqr()?.sum_all()?.to_scalar::()? as f64 + + (&dec.saida - &dec_seq.saida)?.sqr()?.sum_all()?.to_scalar::()? as f64; + assert!(dif < 1e-9, "paralelo deve ser bit-a-bit igual ao sequencial (dif={dif})"); + let speedup = t_seq / t_par.max(1e-12); + Ok((enc, dec, t_par, t_seq, speedup)) + } +} + +// --------------------------------------------------------------------------- +// M-R5 — alocação water-filling por QE +// --------------------------------------------------------------------------- + +/// Seleção top-k com capacidade por especialista (v0.8.0 — extraída +/// VERBATIM do loop da `fwd` v0.6.0: mesma ordem de candidatos, mesmos +/// desempates estáveis de `sort_by`). Compartilhada por `fwd` e +/// `forward` ⇒ as duas atribuem EXATAMENTE os mesmos tokens aos +/// mesmos especialistas (base do teste M-R6 — equivalência de valores). +/// Devolve (designação por especialista, carga global por especialista). +fn selecionar_com_capacidade( + probs: &[Vec], + do_papel: &[usize], + n: usize, + n_top: usize, + capacidade: usize, + n_especialistas: usize, +) -> (Vec<(usize, Vec)>, Vec) { + let mut carga = vec![0usize; n_especialistas]; + let mut designacao: Vec<(usize, Vec)> = Vec::new(); + for &ei in do_papel.iter() { + // candidatos ordenados por prob do especialista ei (desc) + let mut cand: Vec<(usize, f32)> = (0..n).map(|i| (i, probs[i][ei])).collect(); + cand.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + let mut escolhidos: Vec = Vec::new(); + for (i, _) in cand { + if carga[ei] >= capacidade { + break; + } + // ei está no top-k(papel) do token i? + let mut ordem: Vec<(f32, usize)> = + do_papel.iter().map(|&g| (probs[i][g], g)).collect(); + ordem.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + let top: Vec = ordem.iter().rev().take(n_top).map(|(_, g)| *g).collect(); + if top.contains(&ei) { + escolhidos.push(i); + carga[ei] += 1; + } + } + if escolhidos.is_empty() { + continue; + } + designacao.push((ei, escolhidos)); + } + (designacao, carga) +} + +/// Aloca `capacidade_total` especialistas entre células com QE `qes` +/// proporcionalmente ao QE (Cauchy–Schwarz: minimiza Σ q_i²/c_i). +/// Devolve c_i ≥ 1 com Σ c_i = capacidade_total (se capacidade ≥ células). +pub fn alocar_por_qe(qes: &[f64], capacidade_total: usize) -> Vec { + let m = qes.len(); + if m == 0 { + return Vec::new(); + } + let soma: f64 = qes.iter().sum::().max(1e-12); + let mut c: Vec = qes + .iter() + .map(|q| ((q / soma) * capacidade_total as f64).round() as usize) + .collect(); + // garante mínimo 1 e corrige a soma por maior resíduo + for ci in c.iter_mut() { + *ci = (*ci).max(1); + } + let mut total: usize = c.iter().sum(); + while total > capacidade_total.max(m) { + // remove da maior alocação + let (mut imax, mut vmax) = (0usize, 0usize); + for (i, &v) in c.iter().enumerate() { + if v > vmax { + vmax = v; + imax = i; + } + } + if c[imax] > 1 { + c[imax] -= 1; + } + total -= 1; + } + while total < capacidade_total { + let (mut imax, mut vmax) = (0usize, 0.0f64); + for (i, (&v, &q)) in c.iter().zip(qes.iter()).enumerate() { + let res = q / (v as f64); + if res > vmax { + vmax = res; + imax = i; + } + } + c[imax] += 1; + total += 1; + } + c +} diff --git a/src/moe_redirecionavel/teste_integrado.rs b/src/moe_redirecionavel/teste_integrado.rs new file mode 100644 index 0000000000000000000000000000000000000000..90a42e4d0b67fa8524ceb5e96ab6a1fcf6bdab7c --- /dev/null +++ b/src/moe_redirecionavel/teste_integrado.rs @@ -0,0 +1,26 @@ +//! `moe_redirecionavel::teste_integrado` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +#[cfg(test)] +mod teste_som_moe_integrado { + use super::*; + + #[test] + fn debug_som_com_moe_caminho() { + let dev = Device::Cpu; + let dados_v: Vec> = (0..32).map(|i| (0..16).map(|j| (i * 7 + j * 3) as f64 / 100.0).collect()).collect(); + let (n, d) = (dados_v.len(), 16); + let t = Tensor::from_vec(dados_v.into_iter().flatten().collect::>(), (n, d), &dev).unwrap() + .to_dtype(candle_core::DType::F32).unwrap(); + let cfg = crate::som4d::CfgSom4D { nx: 2, ny: 2, nz: 2, nt: 2, ..Default::default() }; + let som = crate::som4d::treinar_som4d(&t, &cfg, &dev).unwrap(); + let centroides = som.prototipos.clone(); + let r = refinar_som_com_moe(&t, ¢roides, 30, 1).unwrap(); + println!("qe {} -> {} (wf) | uniforme {}", r.qe_antes, r.qe_depois, r.qe_alocacao_uniforme); + assert!(r.qe_depois < r.qe_antes, "corretor MoE reduz o QE"); + assert!(r.mse_decodificador < r.qe_antes.powi(2), "redirect decodifica"); + } +} diff --git a/src/moe_redirecionavel/testes.rs b/src/moe_redirecionavel/testes.rs new file mode 100644 index 0000000000000000000000000000000000000000..514a6c09f4b3cf46184b074011127d0cb6077502 --- /dev/null +++ b/src/moe_redirecionavel/testes.rs @@ -0,0 +1,209 @@ +//! `moe_redirecionavel::testes` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +#[cfg(test)] +#[allow(deprecated)] // testes usam o oráculo fwd (bug v080-1) de propósito +mod testes { + use super::*; + + fn dados_sinteticos(n: usize, d: usize, semente: u64) -> Tensor { + let mut rng = crate::rng::RngDeterministico::novo(semente); + let v: Vec = (0..n * d).map(|_| rng.normal(0.0, 1.0) as f32).collect(); + Tensor::from_vec(v, (n, d), &Device::Cpu).unwrap() + } + + #[test] + fn mr1_invariancia_sob_redirecionamento() { + let dev = Device::Cpu; + let x = dados_sinteticos(64, 16, 3); + let mut moe = MoeRedirecionavel::novo(16, 6, 2, 64, &dev).unwrap(); + let s1 = moe.fwd(&x, Papel::Codificador).unwrap(); + let perda_antes = s1.perda_aux.to_scalar::().unwrap() as f64; + let carga_antes: usize = s1.carga.iter().sum(); + moe.redirecionar_todos(); + // após trocar TODOS os papéis, a passada Decodificador usa os + // especialistas que eram do codificador — a perda auxiliar do + // SUBCONJUNTO é comparável (mesma distribuição de roteamento) + let s2 = moe.fwd(&x, Papel::Decodificador).unwrap(); + let perda_depois = s2.perda_aux.to_scalar::().unwrap() as f64; + let carga_depois: usize = s2.carga.iter().sum(); + assert_eq!(carga_antes, carga_depois, "capacidade total conservada (M-R1)"); + assert!( + (perda_antes - perda_depois).abs() < 2.0 + 1e-6, + "perda auxiliar da família conservada: {perda_antes:.4} vs {perda_depois:.4}" + ); + } + + #[test] + fn mr2_neutralidade_no_nascimento() { + let dev = Device::Cpu; + let x = dados_sinteticos(32, 8, 5); + let moe = MoeRedirecionavel::novo(8, 4, 2, 32, &dev).unwrap(); + let g = moe.atencao.gate.as_tensor().to_scalar::().unwrap(); + assert!(g == 0.0, "gate nasce zero"); + let y = moe.atencao.fwd(&x).unwrap(); + let dif = (&y - &x).unwrap().sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap(); + assert!(dif == 0.0, "y = x bit-a-bit no nascimento (dif={dif})"); + } + + #[test] + fn mr3_paralelismo_e_determinismo() { + let dev = Device::Cpu; + let x = dados_sinteticos(96, 24, 7); + let moe = MoeRedirecionavel::novo(24, 8, 2, 96, &dev).unwrap(); + let (_enc, _dec, t_par, t_seq, speedup) = moe.fwd_ambos_par(&x).unwrap(); + // determinismo já garantido por assert interno; speedup registrado + assert!(t_par > 0.0 && t_seq > 0.0); + let _ = speedup; + } + + #[test] + fn mr5_water_filling_minimiza_cota() { + // cota Σ q_i²/c_i: water-filling ≤ uniforme (Cauchy–Schwarz) + let qes = vec![0.2, 0.5, 1.0, 0.3]; + let c_wf = alocar_por_qe(&qes, 8); + let c_un = vec![2usize; 4]; + let f = |c: &[usize]| qes.iter().zip(c).map(|(q, ci)| q * q / *ci as f64).sum::(); + assert!( + f(&c_wf) <= f(&c_un) + 1e-9, + "water-filling {c_wf:?} (cota {:.4}) deve ≤ uniforme (cota {:.4})", + f(&c_wf), f(&c_un) + ); + assert_eq!(c_wf.iter().sum::(), 8); + } + + #[test] + fn jepa_moe_aprende() { + let x = dados_sinteticos(64, 12, 11); + let r = treinar_jepa_com_moe(&x, "moe", 60, 0.5, 42).unwrap(); + assert!(r.perda_pred_final < r.perda_pred_inicial, "perda preditiva cai"); + } + + /// **M-R6 (v0.8.0):** equivalência de VALORES entre `fwd` e + /// `forward` — mesma seleção (auxiliar compartilhado), mesma + /// saída, mesma perda auxiliar, mesma carga/descartados. + #[test] + fn mr6_equivalencia_de_valores() { + let dev = Device::Cpu; + for (n, d, e, k, cap, seed) in [ + (64usize, 16usize, 6usize, 2usize, 64usize, 3u64), + (96, 24, 8, 2, 96, 5), + (48, 12, 4, 1, 24, 9), // capacidade apertada: força descartados + ] { + let x = dados_sinteticos(n, d, seed); + let moe = MoeRedirecionavel::novo(d, e, k, cap, &dev).unwrap(); + let s_ref = moe.fwd(&x, Papel::Codificador).unwrap(); + let s_vet = moe.forward(&x, Papel::Codificador).unwrap(); + assert_eq!(s_ref.carga, s_vet.carga, "M-R6: mesma carga (n={n})"); + assert_eq!(s_ref.usados, s_vet.usados, "M-R6: mesmos usados"); + assert_eq!(s_ref.descartados, s_vet.descartados, "M-R6: mesmos descartados"); + let dif = (&s_ref.saida - &s_vet.saida).unwrap() + .abs().unwrap().max_all().unwrap().to_scalar::().unwrap(); + assert!(dif < 1e-4, "M-R6: max|Δsaída| = {dif} (n={n}, cap={cap})"); + let pa = s_ref.perda_aux.to_scalar::().unwrap(); + let pb = s_vet.perda_aux.to_scalar::().unwrap(); + assert!( + (pa - pb).abs() < 1e-4, + "M-R6: perda auxiliar {pa:.6} vs {pb:.6} (n={n})" + ); + } + } + + /// **M-R7 (v0.8.0):** restauração do fluxo de gradiente — bug v080-1. + /// Com a `fwd` (referência de valores) os especialistas e o roteador + /// NÃO recebem gradiente (grafo seccionado por to_vec2/from_vec); com + /// a `forward` TODOS recebem (index_add/index_select vivos). + #[test] + fn mr7_restauracao_do_gradiente() { + let dev = Device::Cpu; + let x = dados_sinteticos(48, 12, 13); + let moe = MoeRedirecionavel::novo(12, 6, 2, 48, &dev).unwrap(); + let norma_grads = |vetorizada: bool| -> (f64, f64) { + let s = if vetorizada { + moe.forward(&x, Papel::Codificador).unwrap() + } else { + moe.fwd(&x, Papel::Codificador).unwrap() + }; + let perda = s.saida.sqr().unwrap().sum_all().unwrap(); + let grads = perda.backward().unwrap(); + let mut n_roteador = 0.0f64; + for v in moe.roteador.vars() { + if let Some(g) = grads.get(&v) { + n_roteador += g.sqr().unwrap().sum_all().unwrap() + .to_scalar::().unwrap() as f64; + } + } + let mut n_experts = 0.0f64; + for e in &moe.especialistas { + for v in e.vars() { + if let Some(g) = grads.get(&v) { + n_experts += g.sqr().unwrap().sum_all().unwrap() + .to_scalar::().unwrap() as f64; + } + } + } + (n_roteador, n_experts) + }; + // (a) o bug v080-1 é REAL: na fwd, roteador e especialistas têm + // gradiente ZERO (grafo seccionado) — documentado, não corrigido na + // fwd (referência de valores preservada por decisão de projeto) + let (r_ref, e_ref) = norma_grads(false); + assert!( + r_ref == 0.0 && e_ref == 0.0, + "M-R7: bug v080-1 documentado — fwd secciona o grafo (roteador {r_ref}, experts {e_ref})" + ); + // (b) a correção: na vetorizada, TODOS recebem gradiente + let (r_vet, e_vet) = norma_grads(true); + assert!( + r_vet > 0.0, + "M-R7: roteador recebe gradiente na vetorizada ({r_vet})" + ); + assert!( + e_vet > 0.0, + "M-R7: especialistas recebem gradiente na vetorizada ({e_vet})" + ); + } + + /// **M-R8 (v0.8.0):** fim-a-fim — com a vetorizada, o treino REALMENTE + /// atualiza os pesos dos especialistas (regressão do bug v080-1: antes + /// só a atenção própria treinava no braço MoE). + #[test] + fn mr8_treino_atualiza_especialistas() { + let dev = Device::Cpu; + let x = dados_sinteticos(48, 12, 17); + let moe = MoeRedirecionavel::novo(12, 6, 2, 48, &dev).unwrap(); + // snapshot com dados PRÓPRIOS (to_vec1) — detach() compartilha o + // storage e o set() do otimizador sobrescreveria a "cópia" + let snapshot: Vec> = moe + .especialistas + .iter() + .map(|e| { + e.vars()[0].as_tensor().flatten_all().unwrap().to_vec1::().unwrap() + }) + .collect(); + // treino curto com gradiente fluindo + let vars = moe.vars(); + let mut opt = crate::treino::AdamWManual::novo(vars.clone(), 3e-3, 0.0); + for _ in 0..5 { + let s = moe.forward(&x, Papel::Codificador).unwrap(); + let perda = s.saida.sqr().unwrap().sum_all().unwrap(); + let grads = crate::treino::grads_achatados(&perda, &vars).unwrap(); + opt.passo(grads); + } + let mut delta_max = 0.0f64; + for (e, antes) in moe.especialistas.iter().zip(snapshot.iter()) { + let agora = e.vars()[0].as_tensor().flatten_all().unwrap() + .to_vec1::().unwrap(); + let dif = agora.iter().zip(antes.iter()).map(|(a, b)| (a - b).abs()).fold(0f32, f32::max); + delta_max = delta_max.max(dif as f64); + } + let _ = &vars; + assert!( + delta_max > 0.0, + "M-R8: especialistas devem mudar no treino (delta_max={delta_max})" + ); + } +} diff --git a/src/moe_redirecionavel/treino.rs b/src/moe_redirecionavel/treino.rs new file mode 100644 index 0000000000000000000000000000000000000000..37f3710b0d287417e857af2a55ce31179add849a --- /dev/null +++ b/src/moe_redirecionavel/treino.rs @@ -0,0 +1,463 @@ +//! `moe_redirecionavel::treino` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +// --------------------------------------------------------------------------- +// Aplicação JEPA — codificador/preditor redirecionáveis com MoE +// --------------------------------------------------------------------------- + +pub struct RelatorioJepaMoe { + pub modo: &'static str, + pub perda_pred_inicial: f64, + pub perda_pred_final: f64, + pub perda_vc_final: f64, + pub std_emb: f64, + pub carga_max: usize, + pub descartados: usize, +} + +/// Treino JEPA compacto com três braços (ablação honesta): +/// - `"pequena"`: codificador/preditor MLP simples (baseline); +/// - `"densa"`: MLP com o TOTAL de parâmetros dos especialistas (capacidade +/// equiparada, SEM roteamento — isola o ganho do roteamento); +/// - `"moe"`: `MoeRedirecionavel` nos dois caminhos (Codificador no +/// codificador, Decodificador no preditor) com atenção própria. +pub fn treinar_jepa_com_moe( + patches: &Tensor, + modo: &'static str, + passos: usize, + frac_mascara: f64, + semente: u64, +) -> Result { + let dev = patches.device(); + let n = patches.dim(0)?; + let d = patches.dim(1)?; + let mut rng = RngDeterministico::novo(semente); + // alvo = patches originais (detach); contexto = patches mascarados + let alvo = patches.detach(); + let contexto = { + let mut v = patches.flatten_all()?.to_vec1::()?; + for x in v.iter_mut() { + if rng.uniforme() < frac_mascara { + *x = 0.0; + } + } + Tensor::from_vec(v, (n, d), &dev)? + }; + let e = 4usize; // especialistas por papel no braço moe + let n_top = 2; + let capacidade = n; + + // braços + enum Braco { + Pequena(MlpJepa, MlpJepa), + Densa(MlpJepa, MlpJepa), + Moe(MoeRedirecionavel), + } + let braco = match modo { + "pequena" => Braco::Pequena( + MlpJepa::novo(d, 2 * d, d, &dev)?, + MlpJepa::novo(d, 2 * d, d, &dev)?, + ), + "densa" => { + // capacidade equiparada: oculta = soma das ocultas dos especialistas + let d_oculta = 2 * d * e; + Braco::Densa( + MlpJepa::novo(d, d_oculta, d, &dev)?, + MlpJepa::novo(d, d_oculta, d, &dev)?, + ) + } + _ => Braco::Moe(MoeRedirecionavel::novo(d, 2 * e, n_top, capacidade, &dev)?), + }; + + let perda_de = |b: &Braco, x: &Tensor| -> Result<(Tensor, Tensor, usize)> { + match b { + Braco::Pequena(e1, p1) => { + let z = e1.fwd(x)?; + let yh = p1.fwd(&z)?; + let dif = (&yh - &alvo)?; + Ok(((dif.sqr()?.sum_all()? / (n as f64 * d as f64))?, + z.sqr()?.mean_all()?, 0)) + } + Braco::Densa(e1, p1) => { + let z = e1.fwd(x)?; + let yh = p1.fwd(&z)?; + let dif = (&yh - &alvo)?; + Ok(((dif.sqr()?.sum_all()? / (n as f64 * d as f64))?, + z.sqr()?.mean_all()?, 0)) + } + Braco::Moe(m) => { + // v0.9.0: forward (ex-fwd_vetorizado) — gradiente flui aos + // especialistas e ao roteador (a fwd seccionava o grafo) + let s_enc = m.forward(x, Papel::Codificador)?; + let s_dec = m.forward(&s_enc.saida, Papel::Decodificador)?; + let dif = (&s_dec.saida - &alvo)?; + let lp = (dif.sqr()?.sum_all()? / (n as f64 * d as f64))?; + // perda total = preditiva + aux balanceamento (ambos papéis) + let lt = (lp.affine(1.0, 0.0)? + s_enc.perda_aux.affine(m.lambda_aux, 0.0)? + + s_dec.perda_aux.affine(m.lambda_aux, 0.0)?)?; + let carga_max = s_enc.carga.iter().copied().fold(0, usize::max) + .max(s_dec.carga.iter().copied().fold(0, usize::max)); + Ok((lt, lp, carga_max)) + } + } + }; + + let (mut opt, vars): (AdamWManual, Vec) = match &braco { + Braco::Pequena(a, b) => { + let v = [a.vars(), b.vars()].concat(); + (AdamWManual::novo(v.clone(), 3e-3, 0.0), v) + } + Braco::Densa(a, b) => { + let v = [a.vars(), b.vars()].concat(); + (AdamWManual::novo(v.clone(), 3e-3, 0.0), v) + } + Braco::Moe(m) => (AdamWManual::novo(m.vars(), 3e-3, 0.0), m.vars()), + }; + // atenção própria: gate nasce 0; sobe 1 passo em 8 (aquecimento honesto + // do mecanismo — T-P5 análogo: neutralidade no nascimento, gradiente flui) + if let Braco::Moe(m) = &braco { + let g0 = m.atencao.gate.as_tensor().to_scalar::()? as f64; + let _ = g0; + } + + let (l0, _, _) = perda_de(&braco, &contexto)?; + let perda_inicial = l0.to_scalar::()? as f64; + let mut perda_final = perda_inicial; + let mut vc_final = 0.0; + let mut carga_max = 0usize; + let mut descartados = 0usize; + let mut std_emb = 0.0; + for passo in 0..passos { + let (lt, lp, cm) = perda_de(&braco, &contexto)?; + if let Braco::Moe(m) = &braco { + // abre o gate gradualmente (0 → 1 em 25% dos passos) + let alvo_g = ((passo as f64 / (passos as f64 / 4.0).max(1.0)).min(1.0)) as f32; + let g_atual = m.atencao.gate.as_tensor().to_scalar::()? as f64; + if (alvo_g as f64 - g_atual).abs() > 1e-9 { + m.atencao.gate.set(&Tensor::new(alvo_g, &dev)?)?; + } + } + let grads = crate::treino::grads_achatados(<, &vars)?; + AdamWManual::clipar(&mut grads.clone(), 1.0); + let mut g2 = grads; + AdamWManual::clipar(&mut g2, 5.0); + opt.passo(g2); + perda_final = lp.to_scalar::()? as f64; + carga_max = cm; + // VC / std do embedding (para o relatório) + let (_, vc, _) = perda_de(&braco, &contexto)?; + vc_final = vc.to_scalar::()? as f64; + std_emb = { + let z = match &braco { + Braco::Pequena(a, _) => a.fwd(&contexto)?, + Braco::Densa(a, _) => a.fwd(&contexto)?, + Braco::Moe(m) => m.forward(&contexto, Papel::Codificador)?.saida, + }; + z.flatten_all()?.to_vec1::()?.iter().map(|x| *x as f64).collect::>() + .chunks(n).map(|c| { + let m = c.iter().sum::() / c.len() as f64; + (c.iter().map(|x| (x - m).powi(2)).sum::() / c.len() as f64).sqrt() + }).sum::() / n as f64 + }; + } + if let Braco::Moe(m) = &braco { + let s = m.forward(&contexto, Papel::Codificador)?; + descartados = s.descartados; + } + Ok(RelatorioJepaMoe { + modo, + perda_pred_inicial: perda_inicial, + perda_pred_final: perda_final, + perda_vc_final: vc_final, + std_emb, + carga_max, + descartados, + }) +} + +// --------------------------------------------------------------------------- +// Aplicação SOM — refinamento residual com alocação por QE (M-R5) +// --------------------------------------------------------------------------- + +pub struct RelatorioSomMoe { + pub qe_antes: f64, + pub qe_depois: f64, + pub qe_alocacao_uniforme: f64, + pub carga_por_celula: Vec, + pub tempo_treino_s: f64, + /// qualidade da DECODIFICAÇÃO após redirecionamento (mse w_bmu → x). + pub mse_decodificador: f64, +} + +/// Refina um SOM 4D treinado com um MoE redirecionável: +/// 1. aloca especialistas por célula ∝ QE (water-filling — M-R5); +/// 2. treina o papel Codificador como corretor residual +/// `x̂ = w_bmu(x) + E(x)`; +/// 3. mede QE′; repete com alocação uniforme (ablação do water-filling); +/// 4. REDIRECIONA os especialistas para Decodificador e treina +/// `x̂ = E(w_bmu)` (reconstrução a partir do código) — mede o MSE. +pub fn refinar_som_com_moe( + dados: &Tensor, + centroides: &[Vec], + passos: usize, + semente: u64, +) -> Result { + use std::time::Instant; + let t0 = Instant::now(); + let dev = dados.device(); + let n = dados.dim(0)?; + let d = dados.dim(1)?; + let m = centroides.len(); + // BMU + QE por célula (tolera F32/F64) + let dados_v: Vec> = match dados.to_vec2::() { + Ok(v) => v, + Err(_) => dados + .to_vec2::()? + .iter() + .map(|l| l.iter().map(|x| *x as f64).collect()) + .collect(), + }; + let bmu: Vec = dados_v + .iter() + .map(|x| { + centroides + .iter() + .enumerate() + .min_by(|(i, a), (j, b)| { + let da: f64 = a.iter().zip(x).map(|(w, v)| (w - v).powi(2)).sum(); + let db: f64 = b.iter().zip(x).map(|(w, v)| (w - v).powi(2)).sum(); + da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal).then(i.cmp(j)) + }) + .map(|(i, _)| i) + .unwrap_or(0) + }) + .collect(); + let mut qe_cel = vec![0f64; m]; + let mut cont_cel = vec![0usize; m]; + for (i, b) in bmu.iter().enumerate() { + let dist: f64 = centroides[*b].iter().zip(&dados_v[i]).map(|(w, v)| (w - v).powi(2)).sum(); + qe_cel[*b] += dist.sqrt(); + cont_cel[*b] += 1; + } + for i in 0..m { + if cont_cel[i] > 0 { + qe_cel[i] /= cont_cel[i] as f64; + } + } + let qe_antes: f64 = dados_v + .iter() + .enumerate() + .map(|(i, b)| { + let w = ¢roides[bmu[i]]; + (w.iter().zip(b).map(|(a, v)| (a - v).powi(2)).sum::()).sqrt() + }) + .sum::() + / n as f64; + + // ---------- braço water-filling ---------- + // capacidade = m células + 8 especialistas EXTRA distribuídos ∝ QE + // (com C < m o water-filling degenera em uniforme — mínimo 1 por célula) + let capacidade_total = m + 8; + let aloca_wf = alocar_por_qe(&qe_cel, capacidade_total); + let qe_wf = treinar_corretor(dados, centroides, &bmu, &aloca_wf, passos, semente, &dev)?; + // ---------- braço uniforme (ablação: MESMO total e MESMA semente — + // única diferença é a alocação; round-robin soma exatamente C) ---------- + let aloca_un = { + let mut a = vec![1usize; m]; + let mut i = 0usize; + while a.iter().sum::() < capacidade_total { + a[i % m] += 1; + i += 1; + } + a + }; + let qe_un = treinar_corretor(dados, centroides, &bmu, &aloca_un, passos, semente, &dev)?; + + // ---------- redirecionamento → decodificador ---------- + // mesma pool de especialistas, agora aprendendo w_bmu → x + let mse_dec = treinar_decodificador(dados, centroides, &bmu, &aloca_wf, passos, semente + 2, &dev)?; + + Ok(RelatorioSomMoe { + qe_antes, + qe_depois: qe_wf, + qe_alocacao_uniforme: qe_un, + carga_por_celula: aloca_wf, + tempo_treino_s: t0.elapsed().as_secs_f64(), + mse_decodificador: mse_dec, + }) +} + +/// Corretor residual: x̂ = w_bmu + E_{célula}(x). Treina com AdamW. +fn treinar_corretor( + dados: &Tensor, + centroides: &[Vec], + bmu: &[usize], + alocacao: &[usize], + passos: usize, + semente: u64, + dev: &Device, +) -> Result { + let n = dados.dim(0)?; + let d = dados.dim(1)?; + // um "especialista" = MlpJepa por CÉLULA (capacidade alocada = nº de + // especialistas extra treinados nessa célula, somados em ensemble) + let n_esp_tot: usize = alocacao.iter().sum(); + let mut ensembles: Vec> = Vec::with_capacity(centroides.len()); + for c in alocacao { + let mut v = Vec::new(); + for _ in 0..(*c).max(1) { + v.push(MlpJepa::novo(d, 4 * d, d, dev)?); + } + ensembles.push(v); + } + let mut vars: Vec = Vec::new(); + for ens in &ensembles { + for e in ens { + vars.extend(e.vars()); + } + } + let mut opt = AdamWManual::novo(vars.clone(), 3e-3, 0.0); + // tensores de apoio + let w_bmu = { + let v: Vec = (0..n).flat_map(|i| centroides[bmu[i]].iter().map(|x| *x as f32).collect::>()).collect(); + Tensor::from_vec(v, (n, d), dev)? + }; + let alvo_res = (dados - &w_bmu)?; + for _ in 0..passos { + let mut soma = Tensor::zeros((), candle_core::DType::F32, dev)?; + for (i, ens) in ensembles.iter().enumerate() { + // tokens da célula i + let idx: Vec = (0..n).filter(|&k| bmu[k] == i).map(|k| k as u32).collect(); + if idx.is_empty() { + continue; + } + let m_idx = idx.len(); + let it = Tensor::from_vec(idx, (m_idx,), dev)?; + let x_c = dados.index_select(&it, 0)?; + let r_c = alvo_res.index_select(&it, 0)?; + // ensemble: média das saídas + let mut y = Tensor::zeros((m_idx, d), candle_core::DType::F32, dev)?; + for e in ens { + y = (y.affine(1.0, 0.0)? + e.fwd(&x_c)?.affine(1.0, 0.0)?)?; + } + let k = ens.len() as f64; + let yh = y.affine(1.0 / k, 0.0)?; + let dif = (&yh - &r_c)?; + soma = (soma.affine(1.0, 0.0)? + dif.sqr()?.sum_all()?.affine(1.0 / (n as f64 * d as f64), 0.0)?)?; + } + let _ = n_esp_tot; + let grads = crate::treino::grads_achatados(&soma, &vars)?; + let mut g = grads; + AdamWManual::clipar(&mut g, 5.0); + opt.passo(g); + } + // QE refinado + let mut qe = 0f64; + for (i, ens) in ensembles.iter().enumerate() { + let idx: Vec = (0..n).filter(|&k| bmu[k] == i).map(|k| k as u32).collect(); + if idx.is_empty() { + continue; + } + let m_idx = idx.len(); + let it = Tensor::from_vec(idx, (m_idx,), dev)?; + let x_c = dados.index_select(&it, 0)?; + let mut y = Tensor::zeros((m_idx, d), candle_core::DType::F32, dev)?; + for e in ens { + y = (y.affine(1.0, 0.0)? + e.fwd(&x_c)?.affine(1.0, 0.0)?)?; + } + let k = ens.len() as f64; + let yh = y.affine(1.0 / k, 0.0)?; + let xhat = (w_bmu.index_select(&it, 0)? + &yh)?; + let dif = (&x_c - &xhat)?; + qe += dif.sqr()?.sum_all()?.to_scalar::()? as f64; + } + Ok((qe / (n as f64 * d as f64)).sqrt() * (d as f64).sqrt()) // QE médio por ponto (norma L2) +} + +/// Decodificador (pós-redirecionamento): x̂ = E(w_bmu) — reconstrução +/// a partir apenas do código (centróide). Mede o MSE final. +fn treinar_decodificador( + dados: &Tensor, + centroides: &[Vec], + bmu: &[usize], + alocacao: &[usize], + passos: usize, + semente: u64, + dev: &Device, +) -> Result { + let _ = semente; + let n = dados.dim(0)?; + let d = dados.dim(1)?; + let mut ensembles: Vec> = Vec::with_capacity(centroides.len()); + for c in alocacao { + let mut v = Vec::new(); + for _ in 0..(*c).max(1) { + v.push(MlpJepa::novo(d, 4 * d, d, dev)?); + } + ensembles.push(v); + } + let mut vars: Vec = Vec::new(); + for ens in &ensembles { + for e in ens { + vars.extend(e.vars()); + } + } + let mut opt = AdamWManual::novo(vars.clone(), 3e-3, 0.0); + let w_bmu = { + let v: Vec = (0..n).flat_map(|i| centroides[bmu[i]].iter().map(|x| *x as f32).collect::>()).collect(); + Tensor::from_vec(v, (n, d), dev)? + }; + for _ in 0..passos { + let mut soma = Tensor::zeros((), candle_core::DType::F32, dev)?; + for (i, ens) in ensembles.iter().enumerate() { + let idx: Vec = (0..n).filter(|&k| bmu[k] == i).map(|k| k as u32).collect(); + if idx.is_empty() { + continue; + } + let m_idx = idx.len(); + let it = Tensor::from_vec(idx, (m_idx,), dev)?; + let w_c = w_bmu.index_select(&it, 0)?; + let x_c = dados.index_select(&it, 0)?; + let mut y = Tensor::zeros((m_idx, d), candle_core::DType::F32, dev)?; + for e in ens { + y = (y.affine(1.0, 0.0)? + e.fwd(&w_c)?.affine(1.0, 0.0)?)?; + } + let k = ens.len() as f64; + let yh = y.affine(1.0 / k, 0.0)?; + let dif = (&yh - &x_c)?; + soma = (soma.affine(1.0, 0.0)? + dif.sqr()?.sum_all()?.affine(1.0 / (n as f64 * d as f64), 0.0)?)?; + } + let grads = crate::treino::grads_achatados(&soma, &vars)?; + let mut g = grads; + AdamWManual::clipar(&mut g, 5.0); + opt.passo(g); + } + // MSE final do decodificador + let mut mse = 0f64; + for (i, ens) in ensembles.iter().enumerate() { + let idx: Vec = (0..n).filter(|&k| bmu[k] == i).map(|k| k as u32).collect(); + if idx.is_empty() { + continue; + } + let m_idx = idx.len(); + let it = Tensor::from_vec(idx, (m_idx,), dev)?; + let w_c = w_bmu.index_select(&it, 0)?; + let x_c = dados.index_select(&it, 0)?; + let mut y = Tensor::zeros((m_idx, d), candle_core::DType::F32, dev)?; + for e in ens { + y = (y.affine(1.0, 0.0)? + e.fwd(&w_c)?.affine(1.0, 0.0)?)?; + } + let k = ens.len() as f64; + let yh = y.affine(1.0 / k, 0.0)?; + let dif = (&yh - &x_c)?; + mse += dif.sqr()?.sum_all()?.to_scalar::()? as f64; + } + Ok(mse / (n as f64 * d as f64)) +} + +pub fn versao() -> &'static str { + "moe-redirecionavel-0.6.0" +} diff --git a/src/pensamento.rs b/src/pensamento.rs deleted file mode 100644 index 35d7ee7a96d86ac5dcd3e7a57a0ad34f945864b8..0000000000000000000000000000000000000000 --- a/src/pensamento.rs +++ /dev/null @@ -1,1192 +0,0 @@ -//! Tecnologia **Pensamento** v0.5.0 — N-redes neurais flexíveis paralelizadas -//! integradas ao NCM, conectadas ao Mecanismo de Atenção, com raciocínio -//! temporal-estratégico (item d = a + b + c). -//! -//! **O que é (motivação do usuário):** Pensamento é semelhante ao -//! pós-treinamento — N-redes paralelas de tentativas cooperativas para -//! resolver o problema do item c (requisição do usuário) quando a -//! informação tem POUCA CONFIANÇA para gerar novo conhecimento (fora dos -//! padrões comuns), a partir de: -//! - **a)** conhecimento aprendido durante o treinamento; -//! - **b)** estado de inferência corrente; -//! - **c)** a requisição (prompt) do usuário; -//! e da ativação de **difusores** (exploração estocástica) e -//! **combinadores/montadores** (re-encaixe de peças por projeção algébrica). -//! -//! **Ciclo d (raciocínio temporal-estratégico):** -//! `planejamento → construção de etapas e distribuição/desmembramento de -//! competências → volta ao planejamento OU envio da resposta ao usuário`. -//! -//! **Provas (completas em `docs/memorias/r-21-provas-pensamento-v050.md`):** -//! - **T-P1 (gatilho mensurável e causal):** o gatilho -//! `1{conf(p) < τ_c}` com `conf = 1 − H(p)/log K` é função apenas da -//! distribuição preditiva CORRENTE — não transporta informação do futuro, -//! preservando a causalidade (mesma estrutura do gate G1 do NCM). -//! - **T-P2 (tentativas paralelas — auto-consistência):** com N tentativas -//! independentes, cada uma correta com prob. `p > 1/2`, a votação por -//! maioria tem `P(erro) ≤ exp(−2N(p−1/2)²)` (Hoeffding); com seleção -//! best-of-N por verificador exato, `P(sucesso) ≥ 1−(1−p)^N`. -//! - **T-P3 (combinador ótimo):** o montador por mínimos quadrados com -//! regularização ridge resolve `r* = (WᵀW+εI)^{−1}Wᵀr_alvo` — o resíduo é -//! o MÍNIMO possível na norma (pseudo-inversa, Teo V-1' do outro agente). -//! - **T-P4 (terminação e soundness do planejador):** o potencial -//! `Φ(k) = B − k` decresce estritamente a cada iteração do ciclo d ⇒ o -//! loop termina em ≤ B iterações (indução bem-fundada); a resposta só é -//! emitida com confiança ≥ τ_c OU com orçamento esgotado (declaração -//! honesta de queda de confiança na telemetria). -//! - **T-P5 (neutralidade no nascimento na atenção):** com o gate de -//! atenção das tentativas inicializado em ZERO, a saída da atenção é -//! bit-a-bit a da atenção base; a perturbação é limitada por -//! `‖Δy‖ ≤ L·|g|·‖W_k z‖` (Lipschitz com constante L do softmax). -//! - **T-P6 (composição d = a + b + c):** o condicionamento de cada -//! tentativa é `cond = [a; b; c]` — a estrutura de independência -//! condicional `p(resposta | a, b, c)` fatoriza pelo estado conjunto; as -//! tentativas só veem o PASSADO (causalidade preservada). - -use candle_core::{Device, Result, Tensor, Var}; -use crate::camadas::Linear; -use crate::jepa::MlpJepa; -use crate::rng::RngDeterministico; -use std::collections::HashMap; - -// --------------------------------------------------------------------------- -// Confiança e gatilho (T-P1) -// --------------------------------------------------------------------------- - -/// Confiança da distribuição preditiva: `conf = 1 − H(softmax(z))/log K`. -/// -/// **T-P1:** conf é função mensurável de `z` (diferenciável exceto em -/// medida zero), invariante a translação dos logits e satisfaz -/// `conf = 1` na massa de Dirac e `conf = 0` na uniforme — gatilho correto -/// para "pouca confiança". -pub fn confianca_de_logits(logits: &Tensor) -> Result { - let p = candle_nn::ops::softmax(logits, candle_core::D::Minus1)?; - let logp = p.log()?; - let entropia = (&p * &logp)?.sum_all()?.to_scalar::()? as f64 * -1.0; - let k = logits.dim(candle_core::D::Minus1)? as f64; - if k <= 1.0 { - return Ok(1.0); - } - Ok(1.0 - entropia / k.ln()) -} - -// --------------------------------------------------------------------------- -// Motor de Pensamento -// --------------------------------------------------------------------------- - -pub struct MotorPensamento { - /// N redes de tentativa (flexíveis: heterogêneas por semente de - /// inicialização e por ruído de exploração). - pub tentativas: Vec, - /// Montador/combinador: concatena as N saídas → resposta agregada. - pub combinador: Linear, - /// Gate de atenção (T-P5): inicializado em ZERO (neutralidade no - /// nascimento). Escalar multiplicativo sobre a contribuição das - /// tentativas na atenção. - pub gate_atencao: Var, - pub n: usize, - pub d_cond: usize, - pub d_saida: usize, - /// limiar de confiança para emitir resposta (T-P4 soundness). - pub limiar_conf: f64, - /// orçamento de iterações do ciclo d (T-P4 terminação). - pub orcamento: usize, -} - -#[derive(Debug, Clone)] -pub enum EventoCiclo { - Planejamento { iteracao: usize, temperatura: f64 }, - DistribuicaoCompetencias { por_tentativa: Vec }, - RePlanejamento { motivo: String }, - Resposta { confianca: f64, iteracoes: usize }, -} - -#[derive(Debug, Clone)] -pub struct ResultadoPensamento { - /// resposta agregada (d_saida,). - pub resposta: Option, - /// candidatos das N tentativas na última iteração (n, d_saida). - pub candidatos: Tensor, - /// pesos do combinador na agregação final (positivos, soma ~1). - pub pesos: Vec, - /// confiança do verificador na resposta emitida (ou da última tentativa). - pub confianca: f64, - /// nº de iterações do ciclo d executadas. - pub iteracoes: usize, - pub trajetoria: Vec, - pub telemetria: HashMap, -} - -impl MotorPensamento { - /// Cria o motor com N tentativas. `d_cond` = dim(a) + dim(b) + dim(c). - pub fn novo( - d_cond: usize, - d_saida: usize, - n: usize, - limiar_conf: f64, - orcamento: usize, - dev: &Device, - ) -> Result { - let n = n.max(1); - let tentativas = (0..n) - .map(|i| { - let mut m = MlpJepa::novo(d_cond, 2 * d_saida, d_saida, dev)?; - // heterogeneidade: re-semeia cada tentativa - let _ = i; - Ok(m) - }) - .collect::>>()?; - let combinador = Linear::sem_vies(d_saida * n, d_saida, 0.05, dev)?; - let gate = Tensor::zeros((), candle_core::DType::F32, dev)?; - Ok(Self { - tentativas, - combinador, - gate_atencao: Var::from_tensor(&gate)?, - n, - d_cond, - d_saida, - limiar_conf, - orcamento, - }) - } - - pub fn vars(&self) -> Vec { - let mut v: Vec = Vec::new(); - for t in &self.tentativas { - v.extend(t.vars()); - } - v.extend(self.combinador.vars()); - v.push(self.gate_atencao.clone()); - v - } - - /// Re-semeia a tentativa `i` com ruído (heterogeneidade estrutural — - /// "redes flexíveis"): injeta perturbação gaussiana nos pesos. - pub fn flexibilizar(&mut self, i: usize, amplitude: f64, rng: &mut RngDeterministico) -> Result<()> { - let t = &mut self.tentativas[i]; - for v in t.vars() { - let forma: Vec = v.dims().to_vec(); - let total: usize = forma.iter().product(); - let ruido: Vec = (0..total).map(|_| rng.normal(0.0, amplitude) as f32).collect(); - let rt = Tensor::from_vec(ruido, forma, v.device())?; - v.set(&(v.as_tensor().affine(1.0, 0.0)? + rt.affine(1.0, 0.0)?)?)?; - } - Ok(()) - } - - /// Candidatos das N tentativas para o condicionamento `cond` (d_cond,) - /// com temperatura de difusor `temp` (ruído exploratório nas saídas). - /// - /// **T-P6:** `cond = [a; b; c]` — as tentativas veem exatamente a união - /// dos três estados; nada do futuro entra. - pub fn candidatos(&self, cond: &Tensor, temp: f64, rng: &mut RngDeterministico) -> Result { - let dev = cond.device(); - let mut saidas: Vec = Vec::with_capacity(self.n); - let cond_b = if cond.dims().len() == 1 { - cond.broadcast_as((1, self.d_cond))? - } else { - cond.clone() - }; - for t in &self.tentativas { - let o = t.fwd(&cond_b)?; - let o = if temp > 0.0 { - let total = self.d_saida; - let ruido: Vec = - (0..total).map(|_| rng.normal(0.0, temp as f64) as f32).collect(); - let rt = Tensor::from_vec(ruido, (1, self.d_saida), &dev)?; - (o.clone() + rt.affine(1.0, 0.0)?)? - } else { - o - }; - saidas.push(o); - } - Ok(Tensor::cat(&saidas, 0)?) // (n, d_saida) - } - - /// **Combinador/montador (T-P3):** agregação por pesos de coerência — - /// tentativas que concordam com o consenso recebem mais peso (voto - /// contínuo), seguido da projeção linear aprendida do combinador. - pub fn agregar(&self, candidatos: &Tensor) -> Result<(Tensor, Vec)> { - let consenso = candidatos.mean_keepdim(0)?; // (1, d) - // pesos de coerência: similaridade de cosseno com o consenso - let dot = candidatos.matmul(&consenso.transpose(0, 1)?)?; // (n, 1) - let nc = candidatos.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?; - let ncs = consenso.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?; - let cos = dot.broadcast_div(&nc.broadcast_mul(&ncs)?)?; // (n, 1) - let cos_v = cos.flatten_all()?.to_vec1::()?; - let exps: Vec = cos_v.iter().map(|c| (*c as f64 * 4.0).exp()).collect(); - let soma: f64 = exps.iter().sum(); - let pesos: Vec = exps.iter().map(|e| e / soma).collect(); - // média ponderada = "peças re-encaixadas" - let mut media = Tensor::zeros((1, self.d_saida), candle_core::DType::F32, candidatos.device())?; - for (i, p) in pesos.iter().enumerate() { - let linha = candidatos.get(i)?.broadcast_as((1, self.d_saida))?; - media = (media.affine(1.0, 0.0)? + linha.affine(*p, 0.0)?)?; - } - // montagem final pelo combinador aprendido - let concat = candidatos.flatten_all()?.broadcast_as((1, self.d_saida * self.n))?; - let montado = self.combinador.fwd(&concat)?; - // mistura 50/50: montagem aprendida + consenso ponderado (o - // combinador pode especializar-se; o consenso dá robustez T-P2) - let resposta = (montado.affine(0.5, 0.0)? + media.affine(0.5, 0.0)?)?; - Ok((resposta, pesos)) - } - - /// **Ciclo d completo (T-P4):** planejar → distribuir → agregar → - /// avaliar → (re-planejar | responder). O verificador externo devolve a - /// confiança `[0,1]` de uma resposta candidata. - pub fn raciocinar( - &mut self, - cond: &Tensor, - verificador: &dyn Fn(&Tensor) -> f64, - rng: &mut RngDeterministico, - ) -> Result { - let mut trajetoria = Vec::new(); - let mut telemetria: HashMap = HashMap::new(); - let mut temperatura = 0.1f64; - let mut melhor = (None::, -1.0f64, Vec::::new()); - let mut iteracoes = 0usize; - let mut candidatos_final = Tensor::zeros( - (self.n, self.d_saida), - candle_core::DType::F32, - cond.device(), - )?; - - loop { - iteracoes += 1; - // (1) PLANEJAMENTO: gerar candidatos com a temperatura corrente - trajetoria.push(EventoCiclo::Planejamento { iteracao: iteracoes, temperatura }); - let candidatos = self.candidatos(cond, temperatura, rng)?; - candidatos_final = candidatos.clone(); - // (2) DISTRIBUIÇÃO/DESMEMBRAMENTO DE COMPETÊNCIAS: pesos por - // coerência (quem concorda mais lidera a montagem) - let (resposta, pesos) = self.agregar(&candidatos)?; - trajetoria.push(EventoCiclo::DistribuicaoCompetencias { por_tentativa: pesos.clone() }); - // (3) AVALIAÇÃO pelo verificador - let conf = verificador(&resposta); - telemetria.insert(format!("conf_iter_{}", iteracoes), conf); - if conf > melhor.1 { - melhor = (Some(resposta), conf, pesos.clone()); - } - // (4) DECISÃO: responder ou re-planejar - if conf >= self.limiar_conf { - trajetoria.push(EventoCiclo::Resposta { confianca: conf, iteracoes }); - telemetria.insert("iteracoes".into(), iteracoes as f64); - telemetria.insert("confianca_final".into(), conf); - telemetria.insert("temperatura_final".into(), temperatura); - return Ok(ResultadoPensamento { - resposta: melhor.0, - candidatos: candidatos_final, - pesos: melhor.2, - confianca: conf, - iteracoes, - trajetoria, - telemetria, - }); - } - if iteracoes >= self.orcamento { - // Φ = B − k atingiu 0: termina SEM confiança — honestidade - trajetoria.push(EventoCiclo::Resposta { confianca: melhor.1, iteracoes }); - telemetria.insert("iteracoes".into(), iteracoes as f64); - telemetria.insert("confianca_final".into(), melhor.1); - telemetria.insert("temperatura_final".into(), temperatura); - telemetria.insert("orcamento_esgotado".into(), 1.0); - return Ok(ResultadoPensamento { - resposta: melhor.0, - candidatos: candidatos_final, - pesos: melhor.2, - confianca: melhor.1, - iteracoes, - trajetoria, - telemetria, - }); - } - // (5) RE-PLANEJAMENTO: aumenta a exploração (difusor mais quente) - temperatura = (temperatura * 1.6).min(2.0); - trajetoria.push(EventoCiclo::RePlanejamento { - motivo: format!("confiança {conf:.3} < limiar {}", self.limiar_conf), - }); - } - } - - /// **Perda de treino das tentativas + combinador (item a):** treino - /// cooperativo — cada tentativa minimiza sua perda contra o alvo E o - /// combinador minimiza a perda da agregação (o gradiente flui para - /// todos através do concat). - pub fn perda_treino(&self, cond: &Tensor, alvo: &Tensor) -> Result { - // cond (b, d_cond), alvo (b, d_saida) - let b = cond.dim(0)?; - let mut perdas: Vec = Vec::new(); - let mut saidas: Vec = Vec::with_capacity(self.n); - for t in &self.tentativas { - let o = t.fwd(cond)?; - let dif = (&o - alvo)?; - perdas.push((dif.sqr()?.sum_all()? / (self.d_saida as f64 * b as f64))?); - saidas.push(o); - } - let concat = Tensor::cat(&saidas, candle_core::D::Minus1)?; // (b, n·d) - let montado = self.combinador.fwd(&concat)?; - let dif_m = (&montado - alvo)?; - perdas.push((dif_m.sqr()?.sum_all()? / (self.d_saida as f64 * b as f64))?); - // soma das perdas (cooperação: todas as redes + montador) - let mut total = perdas[0].clone(); - for p in perdas.iter().skip(1) { - total = (total + p.clone())?; - } - Ok(total) - } - - /// **Integração com o mecanismo de atenção (T-P5):** anexa as saídas das - /// tentativas como chaves/valores extra com gate escalar. - /// - /// Com `gate = 0` (nascimento): saída IDÊNTICA à atenção base - /// (neutralidade); o gradiente flui pelo gate (não-degenerado). - pub fn atencao_integrada( - &self, - q: &Tensor, - k_base: &Tensor, - v_base: &Tensor, - candidatos: &Tensor, - ) -> Result { - // q (b, n_q, d), k_base (b, n_kv, d), v_base (b, n_kv, d_v), - // candidatos (n_tent, d) — projetados para d e d_v - let dev = q.device(); - let d = k_base.dim(candle_core::D::Minus1)?; - let d_v = v_base.dim(candle_core::D::Minus1)?; - let b = q.dim(0)?; - // projeta candidatos (média simples — sem parâmetros extra): os - // candidatos têm d_saida; se d == d_saida usa direto, senão padding/ - // truncamento honesto - let cand = if self.d_saida == d { - candidatos.clone() - } else if self.d_saida > d { - let v = candidatos - .flatten_all()? - .to_vec1::()? - .chunks(self.d_saida) - .map(|c| c[..d].to_vec()) - .flatten() - .collect::>(); - Tensor::from_vec(v, (self.n, d), &dev)? - } else { - let mut v = candidatos.flatten_all()?.to_vec1::()?; - let mut linhas = Vec::new(); - for i in 0..self.n { - let mut linha = v[i * self.d_saida..(i + 1) * self.d_saida].to_vec(); - linha.extend(std::iter::repeat(0f32).take(d - self.d_saida)); - linhas.push(linha); - } - v = linhas.into_iter().flatten().collect(); - Tensor::from_vec(v, (self.n, d), &dev)? - }; - let k_extra = cand.broadcast_as((b, self.n, d))?; - let v_extra = if d_v == d { - k_extra.clone() - } else { - // projeção simples por média de colunas quando d_v != d - let media = k_extra.mean_keepdim(candle_core::D::Minus1)?; - media.broadcast_as((b, self.n, d_v))? - }; - // atenção sobre [base; extra] - let k = Tensor::cat(&[k_base.clone(), k_extra], 1)?; - let v = Tensor::cat(&[v_base.clone(), v_extra], 1)?; - let scores = (q.matmul(&k.transpose(1, 2)?)? / (d as f64).sqrt())?; - let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1)?; - let y_base_e_extra = attn.matmul(&v)?; - // atenção apenas-base (para neutralidade: gate = 0 ⇒ saída = base) - let scores_base = (q.matmul(&k_base.transpose(1, 2)?)? / (d as f64).sqrt())?; - let attn_base = candle_nn::ops::softmax(&scores_base, candle_core::D::Minus1)?; - let y_base = attn_base.matmul(v_base)?; - // mistura: y = y_base + gate·(y_completo − y_base) - let g = self.gate_atencao.as_tensor(); - let delta = (&y_base_e_extra - &y_base)?; - let forma: Vec = delta.dims().to_vec(); - let delta = (delta.affine(1.0, 0.0)? * g.broadcast_as(forma)?)?; - Ok((&y_base + &delta)?) - } - - // ----------------------------------------------------------------------- - // v0.6.0 — verificador NCM (sem oráculo) + tentativas PARALELAS - // ----------------------------------------------------------------------- - - /// Candidatos das N tentativas em PARALELO (rayon) — as N redes têm - /// parâmetros disjuntos e entradas idênticas ⇒ N sub-DAGs independentes - /// no grafo de computação (mesma estrutura da prova M-R3): tempo - /// paralelo = max_i T_i + sobrecarga de agendamento. - /// Determinismo: o ruído da tentativa i usa a semente `semente + i` - /// (fluxos independentes por construção — reprodutível em qualquer - /// nº de threads). - pub fn candidatos_par( - &self, - cond: &Tensor, - temp: f64, - semente: u64, - ) -> Result<(Tensor, f64)> { - use rayon::prelude::*; - let dev = cond.device(); - let cond_b = if cond.dims().len() == 1 { - cond.broadcast_as((1, self.d_cond))? - } else { - cond.clone() - }; - let t0 = std::time::Instant::now(); - let saidas: Vec = self - .tentativas - .par_iter() - .enumerate() - .map(|(i, t)| -> Result { - let o = t.fwd(&cond_b)?; - if temp > 0.0 { - let mut rng = RngDeterministico::novo(semente + i as u64); - let ruido: Vec = - (0..self.d_saida).map(|_| rng.normal(0.0, temp) as f32).collect(); - let rt = Tensor::from_vec(ruido, (1, self.d_saida), &dev)?; - Ok((&o + &rt.affine(1.0, 0.0)?)?) - } else { - Ok(o) - } - }) - .collect::>>()?; - let dt = t0.elapsed().as_secs_f64(); - Ok((Tensor::cat(&saidas, 0)?, dt)) // (n, d_saida), tempo paralelo - } - - /// **Variante em LOTE (v0.6.0 — fase C):** cond (b, d_cond) → - /// candidatos (b, n, d_saida) — as N tentativas processam o lote - /// inteiro (paralelas entre si; ruído por (linha, tentativa) com - /// semente determinística `semente + i`). - pub fn candidatos_lote(&self, cond: &Tensor, temp: f64, semente: u64) -> Result { - let b = cond.dim(0)?; - let dev = cond.device(); - let mut saidas: Vec = Vec::with_capacity(self.n); - for (i, t) in self.tentativas.iter().enumerate() { - let o = t.fwd(cond)?; // (b, d_saida) - let o = if temp > 0.0 { - let mut rng = RngDeterministico::novo(semente + i as u64); - let ruido: Vec = (0..b * self.d_saida) - .map(|_| rng.normal(0.0, temp) as f32) - .collect(); - let rt = Tensor::from_vec(ruido, (b, self.d_saida), &dev)?; - (&o + &rt)? - } else { - o - }; - saidas.push(o.unsqueeze(1)?); // (b, 1, d_saida) - } - Ok(Tensor::cat(&saidas, 1)?) // (b, n, d_saida) - } - - /// **Agregação em LOTE (v0.6.0):** candidatos (b, n, d) → resposta - /// (b, d) — mesma mistura 50/50 (montagem aprendida + consenso - /// ponderado por coerência) da versão 1-D, VETORIZADA e totalmente - /// diferenciável (gradiente flui pelos DOIS caminhos). Devolve também - /// os pesos médios (telemetria). - pub fn agregar_lote(&self, candidatos: &Tensor) -> Result<(Tensor, Vec)> { - let b = candidatos.dim(0)?; - let n = candidatos.dim(1)?; - let d = candidatos.dim(2)?; - let dev = candidatos.device(); - // consenso (b, d) - let consenso = candidatos.mean(1)?; - // cosseno por (b, n): ⟨cand_i, consenso⟩ / (‖cand_i‖·‖consenso‖) - let dot = candidatos.matmul(&consenso.unsqueeze(2)?)?; // (b, n, 1) - let nc = candidatos.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?; // (b, n, 1) - let ncs = consenso.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?.unsqueeze(1)?; // (b, 1, 1) - let cos = dot.broadcast_div(&nc.broadcast_mul(&ncs)?)?; // (b, n, 1) - // pesos softmax de coerência (b, n, 1) — tensor puro (grafo intacto) - let exps = cos.affine(4.0, 0.0)?.exp()?; - let soma = exps.sum_keepdim(1)?; // (b, 1, 1) - let pesos = exps.broadcast_div(&soma)?; // (b, n, 1) - // média ponderada: Σ_n pesos·cand → (b, d) - let media = candidatos.broadcast_mul(&pesos)?.sum(1)?; // (b, d) - // montagem aprendida: concat sobre n → combinador - let concat = candidatos.reshape((b, n * d))?; - let montado = self.combinador.fwd(&concat)?; // (b, d) - let resposta = (montado.affine(0.5, 0.0)? + media.affine(0.5, 0.0)?)?; - // pesos médios para telemetria (leitura — não afeta o grafo) - let pesos_v = pesos.flatten_all()?.to_vec1::()?; - let mut pesos_medios = vec![0f64; n]; - for (k, p) in pesos_v.iter().enumerate() { - pesos_medios[k % n] += *p as f64 / b as f64; - } - let _ = d; - let _ = dev; - Ok((resposta, pesos_medios)) - } - - /// **Ciclo d com o VERIFICADOR NCM (v0.6.0 — fecha o débito r-21 §3.1).** - /// - /// `logits_fn` mapeia uma resposta candidata (1, d_saida) → logits da - /// distribuição preditiva do BACKBONE condicionada à correção (passada - /// corrente — V-4 causal). O verificador calibrado devolve a confiança; - /// NENHUM acesso ao alvo verdadeiro (o oráculo morre aqui). - pub fn raciocinar_ncm( - &mut self, - cond: &Tensor, - verificador: &crate::verificador::VerificadorNcm, - logits_fn: &dyn Fn(&Tensor) -> Result, - rng: &mut RngDeterministico, - ) -> Result { - let mut trajetoria = Vec::new(); - let mut telemetria: HashMap = HashMap::new(); - let mut temperatura = 0.1f64; - let mut melhor = (None::, -1.0f64, Vec::::new()); - let mut iteracoes = 0usize; - let mut candidatos_final = Tensor::zeros( - (self.n, self.d_saida), - candle_core::DType::F32, - cond.device(), - )?; - loop { - iteracoes += 1; - trajetoria.push(EventoCiclo::Planejamento { iteracao: iteracoes, temperatura }); - let candidatos = self.candidatos(cond, temperatura, rng)?; - candidatos_final = candidatos.clone(); - let (resposta, pesos) = self.agregar(&candidatos)?; - trajetoria.push(EventoCiclo::DistribuicaoCompetencias { por_tentativa: pesos.clone() }); - // AVALIAÇÃO pelo VERIFICADOR NCM (sem oráculo): confiança - // calibrada da distribuição preditiva do backbone - let logits = logits_fn(&resposta)?; - let conf = verificador.confianca_1d(&logits)?; - telemetria.insert(format!("conf_iter_{}", iteracoes), conf); - if conf > melhor.1 { - melhor = (Some(resposta), conf, pesos.clone()); - } - if conf >= self.limiar_conf { - trajetoria.push(EventoCiclo::Resposta { confianca: conf, iteracoes }); - telemetria.insert("iteracoes".into(), iteracoes as f64); - telemetria.insert("confianca_final".into(), conf); - telemetria.insert("temperatura_final".into(), temperatura); - telemetria.insert("verificador".into(), 1.0); // ncm (sem oráculo) - return Ok(ResultadoPensamento { - resposta: melhor.0, - candidatos: candidatos_final, - pesos: melhor.2, - confianca: conf, - iteracoes, - trajetoria, - telemetria, - }); - } - if iteracoes >= self.orcamento { - trajetoria.push(EventoCiclo::Resposta { confianca: melhor.1, iteracoes }); - telemetria.insert("iteracoes".into(), iteracoes as f64); - telemetria.insert("confianca_final".into(), melhor.1); - telemetria.insert("temperatura_final".into(), temperatura); - telemetria.insert("orcamento_esgotado".into(), 1.0); - telemetria.insert("verificador".into(), 1.0); - return Ok(ResultadoPensamento { - resposta: melhor.0, - candidatos: candidatos_final, - pesos: melhor.2, - confianca: melhor.1, - iteracoes, - trajetoria, - telemetria, - }); - } - temperatura = (temperatura * 1.6).min(2.0); - trajetoria.push(EventoCiclo::RePlanejamento { - motivo: format!("conf NCM {conf:.3} < limiar {}", self.limiar_conf), - }); - } - } -} - -// --------------------------------------------------------------------------- -// Combinador algébrico (T-P3 — projeção ridge, Teo V-1' portado) -// --------------------------------------------------------------------------- - -/// Projeção ridge: `r* = (WᵀW + εI)^{−1} Wᵀ r_alvo` — minimizador de -/// `‖W r − r_alvo‖² + ε‖r‖²` (o montador algébrico do "re-encaixe de peças"). -/// -/// **Prova (T-P3):** a função é quadrática estritamente convexa (ε > 0); -/// o gradiente anula-se exatamente em `r*` acima; o resíduo mínimo é -/// `‖(I − W(WᵀW+εI)^{−1}Wᵀ) r_alvo‖` — análogo direto do Teo V-1'. -pub fn projetar_ridge(w: &Tensor, r_alvo: &Tensor, eps: f64) -> Result { - // w: (m, n), r_alvo: (m, d) ⇒ r*: (n, d) - let n = w.dim(1)?; - let dev = w.device(); - let wt = w.transpose(0, 1)?; - let wtw = wt.matmul(w)?; - let ident = Tensor::eye(n, candle_core::DType::F32, &dev)?.affine(eps, 0.0)?; - let m = (&wtw + &ident)?; - let rhs = wt.matmul(r_alvo)?; - resolver_simetrica(&m, &rhs) -} - -/// Resolve `M x = B` para M simétrica (n, n) por eliminação de Gauss com -/// pivoteamento parcial (Cholesky não assumido — M pode ser só PSD+εI). -pub fn resolver_simetrica(m: &Tensor, b: &Tensor) -> Result { - let n = m.dim(0)?; - let d = b.dim(1)?; - let dev = m.device(); - let mut a = m.to_vec2::()?; - let mut rhs = b.to_vec2::()?; - for col in 0..n { - // pivoteamento parcial - let (mut pivo, mut linha_p) = (a[col][col].abs(), col); - for r in col + 1..n { - if a[r][col].abs() > pivo { - pivo = a[r][col].abs(); - linha_p = r; - } - } - if pivo < 1e-12 { - candle_core::bail!("matriz singular (pivo ~ 0 na coluna {col})"); - } - a.swap(col, linha_p); - rhs.swap(col, linha_p); - for r in col + 1..n { - let f = a[r][col] / a[col][col]; - for c in col..n { - a[r][c] -= f * a[col][c]; - } - for c in 0..d { - rhs[r][c] -= f * rhs[col][c]; - } - } - } - let mut x = vec![vec![0f32; d]; n]; - for r in (0..n).rev() { - for c in 0..d { - let mut s = rhs[r][c]; - for k in r + 1..n { - s -= a[r][k] * x[k][c]; - } - x[r][c] = s / a[r][r]; - } - } - let flat: Vec = x.into_iter().flatten().collect(); - Ok(Tensor::from_vec(flat, (n, d), &dev)?) -} - -pub fn versao() -> &'static str { - "pensamento-0.7.0" -} - -// --------------------------------------------------------------------------- -// QUEBRA-CABEÇA pós-treinamento (v0.7.0 — inovação com o conhecimento -// treinado; requisito do usuário; provas Q-1..Q-6 em r-31) -// --------------------------------------------------------------------------- - -/// Configuração do quebra-cabeça (r-28 §4.3). -#[derive(Clone, Copy, Debug)] -pub struct ConfigQuebraCabeca { - /// K peças em que o conhecimento é quebrado. - pub n_pecas: usize, - /// orçamento B de rodadas (terminação Q-4). - pub n_rodadas_max: usize, - /// limiar de validade (mesmo τ_c calibrado do verificador — Q-2). - pub tau_c: f64, - /// teto do ajuste por componente na árvore (regulação). - pub delta_max: f64, - /// remonta as peças com mistura ORTOGONAL por peça (rotação/ortogonalidade). - pub rotacao_ortogonal: bool, - /// para a execução após este nº de rodadas consecutivas sem aceite. - pub paciencia: usize, -} - -impl Default for ConfigQuebraCabeca { - fn default() -> Self { - ConfigQuebraCabeca { - n_pecas: 4, - n_rodadas_max: 12, - tau_c: 0.55, - delta_max: 0.05, - rotacao_ortogonal: true, - paciencia: 5, - } - } -} - -impl ConfigQuebraCabeca { - pub fn novo(n_pecas: usize, n_rodadas: usize, tau_c: f64) -> Self { - ConfigQuebraCabeca { - n_pecas, - n_rodadas_max: n_rodadas, - tau_c, - delta_max: 0.05, - rotacao_ortogonal: true, - paciencia: (n_rodadas / 2).max(2), - } - } -} - -/// Uma rodada do quebra-cabeça. -#[derive(Debug, Clone)] -pub struct RodadaQuebraCabeca { - pub rodada: usize, - /// permutação das peças aplicada na remontagem (reordenação). - pub permutacao_pecas: Vec, - /// confiança NCM do candidato remontado (reavaliação). - pub confianca: f64, - /// conf ≥ τ_c ⇒ VÁLIDO (ajusta a árvore); senão PUNIDO (descarta). - pub aceito: bool, - /// delta aplicado na árvore (0 quando descartado). - pub delta_aplicado: f64, - /// rotações ortogonais usadas (quando ativas) — por peça. - pub usou_rotacao: bool, -} - -/// Resultado consolidado do quebra-cabeça. -#[derive(Debug, Clone)] -pub struct ResultadoQuebraCabeca { - pub rodadas: Vec, - pub aceitos: usize, - pub rejeitados: usize, - pub confianca_base: f64, - pub confianca_melhor: f64, - pub telemetria: HashMap, -} - -/// **Quebra-cabeça de inovação pós-treinamento** (r-28 §4.3): -/// 1. quebra o conhecimento treinado `conceitos` (m, d) em K peças por -/// permutação aleatória das linhas (Fisher–Yates com RNG determinístico); -/// 2. remonta as peças em nova ordem (e com mistura ortogonal por peça -/// quando `rotacao_ortogonal`) — "descobrir novas possibilidades"; -/// 3. reavalia cada remontagem pelo verificador NCM (calibrado — V-2); -/// VÁLIDO (conf ≥ τ_c) ⇒ ajuste regulado na árvore de pesos indexada -/// (nó "pensamento", crédito fundo-topo); PUNIDO ⇒ descarta e o incremento -/// de punição sobe no nó (alimenta o gate do FNH — integração v0.7.0); -/// 4. repete até o orçamento B ou a paciência esgotar (Q-4). -/// -/// **Provas (r-31):** Q-1 cobertura (permutações uniformes ⇒ S_K no suporte; -/// rotações varrem O(d)); Q-2 soundness herdada (mesmo limiar calibrado do -/// verificador — P(aceite inválido) ≤ 1−τ_c+ECE); Q-3 potencial monotônico -/// (Φ_r = Σ conf dos aceitos é não-decrescente; rejeições não tocam Φ); -/// Q-4 terminação (B finito, rodadas O(m·d)); Q-5 invariantes da árvore -/// preservadas (ajustes só tocam métricas, nunca a estrutura — T-A1 intacto; -/// agregação pós-ajuste mantém T-A2); Q-6 causalidade (permutação e confiança -/// medidas antes do ajuste — filtração F_r). -pub fn quebra_cabeca_inovar( - conceitos: &Tensor, - verificador: &crate::verificador::VerificadorNcm, - logits_fn: &dyn Fn(&Tensor) -> Result, - arvore: &mut crate::arvore_pesos::ArvorePesos, - cfg: &ConfigQuebraCabeca, - rng: &mut RngDeterministico, -) -> Result { - let dev = conceitos.device().clone(); - let (m, d) = (conceitos.dims()[0], conceitos.dims()[1]); - let k = cfg.n_pecas.clamp(1, m); - let no_pens = arvore.no("pensamento").unwrap_or(0); - - // linha de base: confiança do conhecimento original (agregado médio) - let base_agg = conceitos.mean(0)?; - let conf_base = { - let lg = logits_fn(&base_agg)?; - verificador.confianca_1d(&lg)? - }; - - let mut rodadas = Vec::new(); - let mut aceitos = 0usize; - let mut rejeitados = 0usize; - let mut conf_melhor = conf_base; - let mut sem_aceite = 0usize; - - for r in 1..=cfg.n_rodadas_max { - if sem_aceite >= cfg.paciencia { - break; - } - // (1) QUEBRA: permutação de linhas Fisher–Yates (Q-6: amostrada ANTES) - let mut perm: Vec = (0..m).collect(); - for i in (1..m).rev() { - let j = rng.inteiro(i + 1); - perm.swap(i, j); - } - // fatia em K peças contíguas (tamanhos quase iguais) - let tamanho = (m + k - 1) / k; - let mut pecas = Vec::with_capacity(k); - let mut ini = 0usize; - while ini < m { - let fim = (ini + tamanho).min(m); - let idx: Vec = perm[ini..fim].iter().map(|&x| x as u32).collect(); - let pedaco = conceitos.index_select(&Tensor::from_vec(idx, (fim - ini,), &dev)?, 0)?; - pecas.push(pedaco); - ini = fim; - } - // (2) REMONTA: nova ordem aleatória das peças (permutação de peças) - let mut perm_pecas: Vec = (0..pecas.len()).collect(); - for i in (1..pecas.len()).rev() { - let j = rng.inteiro(i + 1); - perm_pecas.swap(i, j); - } - let mut remontada = Vec::with_capacity(pecas.len()); - for &pi in &perm_pecas { - let mut pedaco = pecas[pi].clone(); - if cfg.rotacao_ortogonal && d >= 2 { - // mistura ortogonal da peça: Q ∈ O(d) via polar de gaussiana - // do RNG determinístico (preserva normas — ‖xQ‖_F = ‖x‖_F) - let q = q_ortogonal_deterministica(d, rng, &dev)?; - pedaco = pedaco.matmul(&q)?; - } - remontada.push(pedaco); - } - let candidata = Tensor::cat(&remontada, 0)?; // (m, d) remontado - // (3) REAVALIAÇÃO pelo verificador NCM (Q-2: mesmo limiar calibrado) - let agg = candidata.mean(0)?; - let lg = logits_fn(&agg)?; - let conf = verificador.confianca_1d(&lg)?; - let aceito = conf >= cfg.tau_c; - let delta = if aceito { - // VÁLIDO: ajusta a árvore bidirecional (indexada, com teto) - let escalado = (conf - cfg.tau_c).min(cfg.delta_max); - arvore.aplicar_ajuste_indexado(no_pens, "confianca", escalado, cfg.delta_max) - } else { - // PUNIDO: descarta; a punição sobe no nó (sinal para o gate FNH) - rejeitados += 1; - sem_aceite += 1; - arvore.aplicar_ajuste_indexado(no_pens, "punicao", cfg.delta_max * 0.5, cfg.delta_max); - 0.0 - }; - if aceito { - aceitos += 1; - sem_aceite = 0; - conf_melhor = conf_melhor.max(conf); - } - rodadas.push(RodadaQuebraCabeca { - rodada: r, - permutacao_pecas: perm_pecas, - confianca: conf, - aceito, - delta_aplicado: delta, - usou_rotacao: cfg.rotacao_ortogonal, - }); - } - // crédito fundo-topo após todas as rodadas (T-A2) - arvore.agregar_fundo_topo("confianca"); - arvore.agregar_fundo_topo("punicao"); - - let n_rod = rodadas.len(); - let mut telemetria = HashMap::new(); - telemetria.insert("rodadas".into(), n_rod as f64); - telemetria.insert("aceitos".into(), aceitos as f64); - telemetria.insert("rejeitados".into(), rejeitados as f64); - telemetria.insert("confianca_base".into(), conf_base); - telemetria.insert("confianca_melhor".into(), conf_melhor); - telemetria.insert( - "taxa_aceitacao".into(), - if n_rod > 0 { aceitos as f64 / n_rod as f64 } else { 0.0 }, - ); - telemetria.insert("rotacao_ativa".into(), if cfg.rotacao_ortogonal { 1.0 } else { 0.0 }); - Ok(ResultadoQuebraCabeca { - rodadas, - aceitos, - rejeitados, - confianca_base: conf_base, - confianca_melhor: conf_melhor, - telemetria, - }) -} - -/// Q ∈ O(d) determinística: gaussiana do RNG próprio → retração polar. -/// Ortogonal EXATA (δ_ort ≤ 1e-6) e reprodutível (mesma semente ⇒ mesmo Q). -pub fn q_ortogonal_deterministica( - d: usize, - rng: &mut RngDeterministico, - dev: &Device, -) -> Result { - let n = d * d; - let v: Vec = (0..n).map(|_| rng.normal_padrao() as f32).collect(); - let g = Tensor::from_vec(v, (d, d), dev)?; - crate::stiefel::retrair_polar_frobenius(&g, 90) -} - - -#[cfg(test)] -mod testes { - use super::*; - - #[test] - fn teste_confianca_extremos() { - // T-P1: Dirac ⇒ conf = 1; uniforme ⇒ conf = 0 - let dev = Device::Cpu; - let dirac = Tensor::from_vec(vec![100.0f32, 0.0, 0.0, 0.0], (1, 4), &dev).unwrap(); - let uniforme = Tensor::from_vec(vec![0.0f32; 4], (1, 4), &dev).unwrap(); - let c1 = confianca_de_logits(&dirac).unwrap(); - let c0 = confianca_de_logits(&uniforme).unwrap(); - assert!(c1 > 0.999, "conf dirac = {c1}"); - assert!(c0 < 0.001, "conf uniforme = {c0}"); - } - - #[test] - fn teste_tp3_projetar_ridge_minimiza() { - // T-P3: r* da projeção ridge tem resíduo ≤ qualquer perturbação - let dev = Device::Cpu; - let w = Tensor::randn(0f32, 1.0, (8, 3), &dev).unwrap(); - let r_alvo = Tensor::randn(0f32, 1.0, (8, 5), &dev).unwrap(); - let r = projetar_ridge(&w, &r_alvo, 1e-6).unwrap(); - let residuo = |rr: &Tensor| { - let d = (w.matmul(rr).unwrap() - &r_alvo).unwrap(); - d.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64 - }; - let f0 = residuo(&r); - let mut rng = RngDeterministico::novo(1); - for _ in 0..30 { - let pert: Vec = (0..15).map(|_| rng.normal(0.0, 0.1) as f32).collect(); - let rp = (Tensor::from_vec(pert, (3, 5), &dev).unwrap() + &r).unwrap(); - let fp = residuo(&rp); - assert!(f0 <= fp + 1e-6, "residuo r* = {f0} > perturbado {fp}"); - } - } - - #[test] - fn teste_tp4_terminacao_e_orcamento() { - // o ciclo d SEMPRE termina em ≤ orçamento iterações (potencial - // Φ = B − k); com verificador impossível, termina com - // orcamento_esgotado = 1. - let dev = Device::Cpu; - let mut motor = MotorPensamento::novo(6, 4, 3, 0.99, 5, &dev).unwrap(); - let cond = Tensor::randn(0f32, 1.0, (6,), &dev).unwrap(); - let mut rng = RngDeterministico::novo(2); - let res = motor - .raciocinar(&cond, &|_| 0.0, &mut rng) // verificador sempre 0 - .unwrap(); - assert!(res.iteracoes <= 5, "iterações = {} > orçamento", res.iteracoes); - assert_eq!(res.telemetria.get("orcamento_esgotado"), Some(&1.0)); - assert!(res.resposta.is_some(), "melhor candidato ainda é retornado"); - } - - #[test] - fn teste_tp5_neutralidade_no_nascimento() { - // gate = 0 ⇒ saída da atenção integrada é BIT-A-BIT a da base - let dev = Device::Cpu; - let motor = MotorPensamento::novo(8, 8, 3, 0.5, 3, &dev).unwrap(); - let q = Tensor::randn(0f32, 1.0, (1, 4, 8), &dev).unwrap(); - let k = Tensor::randn(0f32, 1.0, (1, 6, 8), &dev).unwrap(); - let v = Tensor::randn(0f32, 1.0, (1, 6, 8), &dev).unwrap(); - let cand = Tensor::randn(0f32, 1.0, (3, 8), &dev).unwrap(); - let y_int = motor.atencao_integrada(&q, &k, &v, &cand).unwrap(); - // atenção base pura - let scores = (q.matmul(&k.transpose(1, 2).unwrap()).unwrap() / (8f64).sqrt()).unwrap(); - let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1).unwrap(); - let y_base = attn.matmul(&v).unwrap(); - let dif = (&y_int - &y_base).unwrap().abs().unwrap().max_all().unwrap().to_scalar::().unwrap(); - assert!(dif == 0.0, "gate=0 deveria dar bit-a-bit, dif = {dif}"); - } - - #[test] - fn teste_treino_cooperativo_e_agregacao() { - // item a: as tentativas + combinador APRENDEM a função alvo; a - // agregação converge para o alvo melhor que uma tentativa isolada - // no pior caso (verificação numérica honesta em escala pequena). - let dev = Device::Cpu; - let mut motor = MotorPensamento::novo(6, 4, 3, 0.5, 3, &dev).unwrap(); - let vars = motor.vars(); - let mut otim = crate::treino::AdamWManual::novo(vars, 5e-3, 0.0); - let mut rng = RngDeterministico::novo(4); - // alvo: função não-linear determinística de cond - let dados: Vec<(Vec, Vec)> = (0..256) - .map(|_| { - let c: Vec = (0..6).map(|_| rng.normal(0.0, 1.0) as f32).collect(); - let a: Vec = (0..4) - .map(|j| { - (c[j % 6] * 0.5 + (c[(j + 1) % 6] * c[(j + 2) % 6]) * 0.3 + 0.1) as f32 - }) - .collect(); - (c, a) - }) - .collect(); - let cs: Vec = dados.iter().flat_map(|(c, _)| c.clone()).collect(); - let as_: Vec = dados.iter().flat_map(|(_, a)| a.clone()).collect(); - let cond = Tensor::from_vec(cs, (256, 6), &dev).unwrap(); - let alvo = Tensor::from_vec(as_, (256, 4), &dev).unwrap(); - let mut perda0 = f64::NAN; - let mut perda1 = f64::NAN; - for passo in 0..200 { - let perda = motor.perda_treino(&cond, &alvo).unwrap(); - if passo == 0 { - perda0 = perda.to_scalar::().unwrap() as f64; - } - perda1 = perda.to_scalar::().unwrap() as f64; - otim.passo(crate::treino::grads_achatados(&perda, &motor.vars()).unwrap()); - } - assert!(perda1 < perda0 * 0.5, "treino cooperativo: {perda0} → {perda1}"); - // agregação: verificador = negativo do erro quadrático médio - let mut rng2 = RngDeterministico::novo(9); - let cand = motor.candidatos(&cond.get(0).unwrap(), 0.0, &mut rng2).unwrap(); - let (resp, pesos) = motor.agregar(&cand).unwrap(); - assert_eq!(resp.dims().to_vec(), vec![1, 4]); - let soma: f64 = pesos.iter().sum(); - assert!((soma - 1.0).abs() < 1e-9, "pesos somam {soma}"); - assert!(pesos.iter().all(|&p| p >= 0.0)); - } - - #[test] - fn teste_tp2_auto_consistencia_numérica() { - // T-P2 numérico: N tentativas com viés comum pequeno; a média - // agregada tem erro menor que a tentativa mediana (redução de - // variância) na maioria das repetições. - let dev = Device::Cpu; - let mut rng = RngDeterministico::novo(6); - let alvo = [1.0f64, 2.0, -1.0, 0.5]; - let mut venceu = 0; - let reps = 200; - for _ in 0..reps { - let n = 7; - let erros: Vec = (0..n) - .map(|_| { - alvo - .iter() - .map(|&a| { - let ruido = rng.normal(0.0, 1.0); - (a + ruido - a).abs() - }) - .sum::() - }) - .collect(); - let media: Vec = (0..4) - .map(|j| { - (0..n).map(|i| alvo[j] + rng.normal(0.0, 1.0)).sum::() / n as f64 - }) - .collect(); - let erro_media: f64 = media.iter().zip(alvo.iter()).map(|(m, a)| (m - a).abs()).sum(); - let mut ordenado = erros.clone(); - ordenado.sort_by(|a, b| a.partial_cmp(b).unwrap()); - let mediana = ordenado[n / 2]; - if erro_media <= mediana { - venceu += 1; - } - } - // a média vence a mediana na grande maioria dos casos (E[erro da - // média] = σ/√n < σ = E[erro de UMA tentativa]) - assert!( - venceu as f64 / reps as f64 >= 0.6, - "auto-consistência venceu só {}/{}", - venceu, - reps - ); - } - - // ------------------------------------------------------------------ - // Quebra-cabeça (v0.7.0) — gates Q-1..Q-6 (provas r-31) - // ------------------------------------------------------------------ - - fn cenario_quebra_cabeca() - -> (Tensor, crate::verificador::VerificadorNcm, crate::arvore_pesos::ArvorePesos) { - let dev = Device::Cpu; - let mut rng = RngDeterministico::novo(777); - // "conhecimento treinado": 12 conceitos (d=8) com estrutura - let m = 12usize; - let d = 8usize; - let v: Vec = (0..m * d).map(|i| rng.normal_padrao() as f32).collect(); - let conceitos = Tensor::from_vec(v, (m, d), &dev).unwrap(); - // verificador calibrado com um "verificador" sintético: logits - // favoráveis ao agregado médio dos conceitos - let mut verif = crate::verificador::VerificadorNcm::novo(); - let _ = &mut verif; - let mut arvore = crate::arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new()); - let _ = &mut arvore; - (conceitos, verif, arvore) - } - - fn logits_fn_exemplo(x: &Tensor) -> Result { - // logits determinísticos correlacionados com a norma do agregado — - // quanto mais "concentrado" o agregado, maior a confiança - let norma = x.sqr()?.sum_all()?.to_scalar::()? as f64; - let base = (norma * 2.0).min(8.0); - let v: Vec = vec![base as f32, 0.5, 0.2, 0.1]; - Ok(Tensor::from_vec(v, (4,), x.device())?) - } - - /// Q-2/Q-3: todo ACEITO tem conf ≥ τ_c; rejeições não aplicam delta; - /// potencial monotônico (confiança acumulada só cresce nos aceites). - #[test] - fn quebra_cabeca_soundness_e_potencial() -> Result<()> { - let (conceitos, verif, mut arvore) = cenario_quebra_cabeca(); - let cfg = ConfigQuebraCabeca::novo(4, 10, 0.55); - let mut rng = RngDeterministico::novo(4242); - let r = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?; - for rod in &r.rodadas { - if rod.aceito { - assert!( - rod.confianca >= cfg.tau_c, - "Q-2: aceito com conf {} < τ_c {}", - rod.confianca, - cfg.tau_c - ); - assert!(rod.delta_aplicado > 0.0, "aceite aplica delta > 0"); - } else { - assert_eq!(rod.delta_aplicado, 0.0, "Q-3: rejeitado não aplica delta"); - } - } - // potencial monotônico: Σ conf dos aceitos ≤ Σ max(conf, τ_c) das rodadas - let pot: f64 = r.rodadas.iter().filter(|x| x.aceito).map(|x| x.confianca).sum(); - assert!(pot >= 0.0); - // Q-5: invariantes da árvore preservadas - arvore.invariantes_ok().expect("Q-5: invariantes T-A1 preservadas"); - Ok(()) - } - - /// Q-4: terminação — nunca excede n_rodadas_max nem roda sem parar. - #[test] - fn quebra_cabeca_termina() -> Result<()> { - let (conceitos, verif, mut arvore) = cenario_quebra_cabeca(); - let cfg = ConfigQuebraCabeca::novo(3, 6, 0.99); // τ inalcançável - let mut rng = RngDeterministico::novo(1); - let r = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?; - assert!(r.rodadas.len() <= 6, "Q-4: orçamento respeitado"); - assert_eq!(r.aceitos, 0, "τ inalcançável ⇒ nenhum aceite"); - assert!(r.rejeitados > 0); - Ok(()) - } - - /// Q-4b: rejeições incrementam a PUNIÇÃO no nó pensamento (sinal FNH). - #[test] - fn quebra_cabeca_punicao_sobe() -> Result<()> { - let (conceitos, verif, mut arvore) = cenario_quebra_cabeca(); - let no_pens = arvore.no("pensamento").unwrap(); - let antes = arvore.nos[no_pens].metricas.get("punicao").copied().unwrap_or(0.0); - let cfg = ConfigQuebraCabeca::novo(3, 5, 0.999); - let mut rng = RngDeterministico::novo(9); - let _ = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?; - let depois = arvore.nos[no_pens].metricas.get("punicao").copied().unwrap_or(0.0); - assert!(depois > antes, "punição {depois} deve > {antes} após rejeições"); - Ok(()) - } - - /// Rotação/ortogonalidade: ‖xQ‖_F = ‖x‖_F (preserva energia) e a - /// remontagem com rotação difere da sem rotação (exploração real). - #[test] - fn quebra_cabeca_rotacao_preserva_norma() -> Result<()> { - let dev = Device::Cpu; - let mut rng = RngDeterministico::novo(31); - let v: Vec = (0..5 * 6).map(|_| rng.normal_padrao() as f32).collect(); - let x = Tensor::from_vec(v, (5, 6), &dev)?; - let q = q_ortogonal_deterministica(6, &mut rng, &dev)?; - let xq = x.matmul(&q)?; - let nx = x.sqr()?.sum_all()?.to_scalar::()?; - let nxq = xq.sqr()?.sum_all()?.to_scalar::()?; - assert!( - (nx - nxq).abs() / nx < 1e-4, - "‖xQ‖_F = {nxq} deve ≈ ‖x‖_F = {nx} (ortogonalidade)" - ); - // Q é ortogonal exato - let delta = crate::stiefel::erro_ortogonalidade(&q)?; - assert!(delta < 1e-6, "δ_ort(Q) = {delta}"); - // remontagem COM rotação difere da SEM rotação - let d2 = (&xq - &x)?.abs()?.max_all()?.to_scalar::()?; - assert!(d2 > 1e-3, "rotação deve explorar (dif = {d2})"); - Ok(()) - } - - /// Determinismo: mesma semente ⇒ mesmas rodadas (reprodutibilidade). - #[test] - fn quebra_cabeca_deterministico() -> Result<()> { - let (c1, v1, mut a1) = cenario_quebra_cabeca(); - let (_, v2, mut a2) = cenario_quebra_cabeca(); - let cfg = ConfigQuebraCabeca::novo(4, 8, 0.5); - let mut r1 = RngDeterministico::novo(123); - let mut r2 = RngDeterministico::novo(123); - let q1 = quebra_cabeca_inovar(&c1, &v1, &logits_fn_exemplo, &mut a1, &cfg, &mut r1)?; - let q2 = quebra_cabeca_inovar(&c1, &v2, &logits_fn_exemplo, &mut a2, &cfg, &mut r2)?; - assert_eq!(q1.rodadas.len(), q2.rodadas.len()); - for (x, y) in q1.rodadas.iter().zip(q2.rodadas.iter()) { - assert_eq!(x.permutacao_pecas, y.permutacao_pecas, "mesma semente ⇒ mesma remontagem"); - assert!((x.confianca - y.confianca).abs() < 1e-12); - assert_eq!(x.aceito, y.aceito); - } - Ok(()) - } -} diff --git a/src/pensamento/combinador.rs b/src/pensamento/combinador.rs new file mode 100644 index 0000000000000000000000000000000000000000..00e80bfc75a77ad9cf8a6c4705f05d20aa804be1 --- /dev/null +++ b/src/pensamento/combinador.rs @@ -0,0 +1,73 @@ +//! `pensamento::combinador` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +// --------------------------------------------------------------------------- +// Combinador algébrico (T-P3 — projeção ridge, Teo V-1' portado) +// --------------------------------------------------------------------------- + +/// Projeção ridge: `r* = (WᵀW + εI)^{−1} Wᵀ r_alvo` — minimizador de +/// `‖W r − r_alvo‖² + ε‖r‖²` (o montador algébrico do "re-encaixe de peças"). +/// +/// **Prova (T-P3):** a função é quadrática estritamente convexa (ε > 0); +/// o gradiente anula-se exatamente em `r*` acima; o resíduo mínimo é +/// `‖(I − W(WᵀW+εI)^{−1}Wᵀ) r_alvo‖` — análogo direto do Teo V-1'. +pub fn projetar_ridge(w: &Tensor, r_alvo: &Tensor, eps: f64) -> Result { + // w: (m, n), r_alvo: (m, d) ⇒ r*: (n, d) + let n = w.dim(1)?; + let dev = w.device(); + let wt = w.transpose(0, 1)?; + let wtw = wt.matmul(w)?; + let ident = Tensor::eye(n, candle_core::DType::F32, &dev)?.affine(eps, 0.0)?; + let m = (&wtw + &ident)?; + let rhs = wt.matmul(r_alvo)?; + resolver_simetrica(&m, &rhs) +} + +/// Resolve `M x = B` para M simétrica (n, n) por eliminação de Gauss com +/// pivoteamento parcial (Cholesky não assumido — M pode ser só PSD+εI). +pub fn resolver_simetrica(m: &Tensor, b: &Tensor) -> Result { + let n = m.dim(0)?; + let d = b.dim(1)?; + let dev = m.device(); + let mut a = m.to_vec2::()?; + let mut rhs = b.to_vec2::()?; + for col in 0..n { + // pivoteamento parcial + let (mut pivo, mut linha_p) = (a[col][col].abs(), col); + for r in col + 1..n { + if a[r][col].abs() > pivo { + pivo = a[r][col].abs(); + linha_p = r; + } + } + if pivo < 1e-12 { + candle_core::bail!("matriz singular (pivo ~ 0 na coluna {col})"); + } + a.swap(col, linha_p); + rhs.swap(col, linha_p); + for r in col + 1..n { + let f = a[r][col] / a[col][col]; + for c in col..n { + a[r][c] -= f * a[col][c]; + } + for c in 0..d { + rhs[r][c] -= f * rhs[col][c]; + } + } + } + let mut x = vec![vec![0f32; d]; n]; + for r in (0..n).rev() { + for c in 0..d { + let mut s = rhs[r][c]; + for k in r + 1..n { + s -= a[r][k] * x[k][c]; + } + x[r][c] = s / a[r][r]; + } + } + let flat: Vec = x.into_iter().flatten().collect(); + Ok(Tensor::from_vec(flat, (n, d), &dev)?) +} diff --git a/src/pensamento/mod.rs b/src/pensamento/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..5bb72c5c67cc70b7507004c8f659627dff4c7dfe --- /dev/null +++ b/src/pensamento/mod.rs @@ -0,0 +1,71 @@ +//! Tecnologia Pensamento — módulos: motor, combinador ridge, +//! quebra-cabeça pós-treino (divisão SRP v0.10.0 — r-35 §2). + +//! Tecnologia **Pensamento** v0.5.0 — N-redes neurais flexíveis paralelizadas +//! integradas ao NCM, conectadas ao Mecanismo de Atenção, com raciocínio +//! temporal-estratégico (item d = a + b + c). +//! +//! **O que é (motivação do usuário):** Pensamento é semelhante ao +//! pós-treinamento — N-redes paralelas de tentativas cooperativas para +//! resolver o problema do item c (requisição do usuário) quando a +//! informação tem POUCA CONFIANÇA para gerar novo conhecimento (fora dos +//! padrões comuns), a partir de: +//! - **a)** conhecimento aprendido durante o treinamento; +//! - **b)** estado de inferência corrente; +//! - **c)** a requisição (prompt) do usuário; +//! e da ativação de **difusores** (exploração estocástica) e +//! **combinadores/montadores** (re-encaixe de peças por projeção algébrica). +//! +//! **Ciclo d (raciocínio temporal-estratégico):** +//! `planejamento → construção de etapas e distribuição/desmembramento de +//! competências → volta ao planejamento OU envio da resposta ao usuário`. +//! +//! **Provas (completas em `docs/memorias/r-21-provas-pensamento-v050.md`):** +//! - **T-P1 (gatilho mensurável e causal):** o gatilho +//! `1{conf(p) < τ_c}` com `conf = 1 − H(p)/log K` é função apenas da +//! distribuição preditiva CORRENTE — não transporta informação do futuro, +//! preservando a causalidade (mesma estrutura do gate G1 do NCM). +//! - **T-P2 (tentativas paralelas — auto-consistência):** com N tentativas +//! independentes, cada uma correta com prob. `p > 1/2`, a votação por +//! maioria tem `P(erro) ≤ exp(−2N(p−1/2)²)` (Hoeffding); com seleção +//! best-of-N por verificador exato, `P(sucesso) ≥ 1−(1−p)^N`. +//! - **T-P3 (combinador ótimo):** o montador por mínimos quadrados com +//! regularização ridge resolve `r* = (WᵀW+εI)^{−1}Wᵀr_alvo` — o resíduo é +//! o MÍNIMO possível na norma (pseudo-inversa, Teo V-1' do outro agente). +//! - **T-P4 (terminação e soundness do planejador):** o potencial +//! `Φ(k) = B − k` decresce estritamente a cada iteração do ciclo d ⇒ o +//! loop termina em ≤ B iterações (indução bem-fundada); a resposta só é +//! emitida com confiança ≥ τ_c OU com orçamento esgotado (declaração +//! honesta de queda de confiança na telemetria). +//! - **T-P5 (neutralidade no nascimento na atenção):** com o gate de +//! atenção das tentativas inicializado em ZERO, a saída da atenção é +//! bit-a-bit a da atenção base; a perturbação é limitada por +//! `‖Δy‖ ≤ L·|g|·‖W_k z‖` (Lipschitz com constante L do softmax). +//! - **T-P6 (composição d = a + b + c):** o condicionamento de cada +//! tentativa é `cond = [a; b; c]` — a estrutura de independência +//! condicional `p(resposta | a, b, c)` fatoriza pelo estado conjunto; as +//! tentativas só veem o PASSADO (causalidade preservada). + +use candle_core::{Device, Result, Tensor, Var}; +use crate::camadas::Linear; +use crate::jepa::MlpJepa; +use crate::rng::RngDeterministico; +use std::collections::HashMap; + +// ---- divisão SRP v0.10.0 (r-35 §2): submódulos + re-exports ---- +#[cfg(test)] +mod testes; +mod combinador; +mod motor; +mod quebra_cabeca; + +pub use combinador::*; +pub use motor::*; +pub use quebra_cabeca::*; + +// ---- itens mantidos no próprio mod.rs ---- + + +pub fn versao() -> &'static str { + "pensamento-0.7.0" +} diff --git a/src/pensamento/motor.rs b/src/pensamento/motor.rs new file mode 100644 index 0000000000000000000000000000000000000000..bc7f0ddcfa5a9c3b8a1b7aaaba1fc7fc4f4785f5 --- /dev/null +++ b/src/pensamento/motor.rs @@ -0,0 +1,549 @@ +//! `pensamento::motor` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +// --------------------------------------------------------------------------- +// Confiança e gatilho (T-P1) +// --------------------------------------------------------------------------- + +/// Confiança da distribuição preditiva: `conf = 1 − H(softmax(z))/log K`. +/// +/// **T-P1:** conf é função mensurável de `z` (diferenciável exceto em +/// medida zero), invariante a translação dos logits e satisfaz +/// `conf = 1` na massa de Dirac e `conf = 0` na uniforme — gatilho correto +/// para "pouca confiança". +pub fn confianca_de_logits(logits: &Tensor) -> Result { + let p = candle_nn::ops::softmax(logits, candle_core::D::Minus1)?; + let logp = p.log()?; + let entropia = (&p * &logp)?.sum_all()?.to_scalar::()? as f64 * -1.0; + let k = logits.dim(candle_core::D::Minus1)? as f64; + if k <= 1.0 { + return Ok(1.0); + } + Ok(1.0 - entropia / k.ln()) +} + +// --------------------------------------------------------------------------- +// Motor de Pensamento +// --------------------------------------------------------------------------- + +pub struct MotorPensamento { + /// N redes de tentativa (flexíveis: heterogêneas por semente de + /// inicialização e por ruído de exploração). + pub tentativas: Vec, + /// Montador/combinador: concatena as N saídas → resposta agregada. + pub combinador: Linear, + /// Gate de atenção (T-P5): inicializado em ZERO (neutralidade no + /// nascimento). Escalar multiplicativo sobre a contribuição das + /// tentativas na atenção. + pub gate_atencao: Var, + pub n: usize, + pub d_cond: usize, + pub d_saida: usize, + /// limiar de confiança para emitir resposta (T-P4 soundness). + pub limiar_conf: f64, + /// orçamento de iterações do ciclo d (T-P4 terminação). + pub orcamento: usize, +} + +#[derive(Debug, Clone)] +pub enum EventoCiclo { + Planejamento { iteracao: usize, temperatura: f64 }, + DistribuicaoCompetencias { por_tentativa: Vec }, + RePlanejamento { motivo: String }, + Resposta { confianca: f64, iteracoes: usize }, +} + +#[derive(Debug, Clone)] +pub struct ResultadoPensamento { + /// resposta agregada (d_saida,). + pub resposta: Option, + /// candidatos das N tentativas na última iteração (n, d_saida). + pub candidatos: Tensor, + /// pesos do combinador na agregação final (positivos, soma ~1). + pub pesos: Vec, + /// confiança do verificador na resposta emitida (ou da última tentativa). + pub confianca: f64, + /// nº de iterações do ciclo d executadas. + pub iteracoes: usize, + pub trajetoria: Vec, + pub telemetria: HashMap, +} + +impl MotorPensamento { + /// Cria o motor com N tentativas. `d_cond` = dim(a) + dim(b) + dim(c). + pub fn novo( + d_cond: usize, + d_saida: usize, + n: usize, + limiar_conf: f64, + orcamento: usize, + dev: &Device, + ) -> Result { + let n = n.max(1); + let tentativas = (0..n) + .map(|i| { + let mut m = MlpJepa::novo(d_cond, 2 * d_saida, d_saida, dev)?; + // heterogeneidade: re-semeia cada tentativa + let _ = i; + Ok(m) + }) + .collect::>>()?; + let combinador = Linear::sem_vies(d_saida * n, d_saida, 0.05, dev)?; + let gate = Tensor::zeros((), candle_core::DType::F32, dev)?; + Ok(Self { + tentativas, + combinador, + gate_atencao: Var::from_tensor(&gate)?, + n, + d_cond, + d_saida, + limiar_conf, + orcamento, + }) + } + + pub fn vars(&self) -> Vec { + let mut v: Vec = Vec::new(); + for t in &self.tentativas { + v.extend(t.vars()); + } + v.extend(self.combinador.vars()); + v.push(self.gate_atencao.clone()); + v + } + + /// Re-semeia a tentativa `i` com ruído (heterogeneidade estrutural — + /// "redes flexíveis"): injeta perturbação gaussiana nos pesos. + pub fn flexibilizar(&mut self, i: usize, amplitude: f64, rng: &mut RngDeterministico) -> Result<()> { + let t = &mut self.tentativas[i]; + for v in t.vars() { + let forma: Vec = v.dims().to_vec(); + let total: usize = forma.iter().product(); + let ruido: Vec = (0..total).map(|_| rng.normal(0.0, amplitude) as f32).collect(); + let rt = Tensor::from_vec(ruido, forma, v.device())?; + v.set(&(v.as_tensor().affine(1.0, 0.0)? + rt.affine(1.0, 0.0)?)?)?; + } + Ok(()) + } + + /// Candidatos das N tentativas para o condicionamento `cond` (d_cond,) + /// com temperatura de difusor `temp` (ruído exploratório nas saídas). + /// + /// **T-P6:** `cond = [a; b; c]` — as tentativas veem exatamente a união + /// dos três estados; nada do futuro entra. + pub fn candidatos(&self, cond: &Tensor, temp: f64, rng: &mut RngDeterministico) -> Result { + let dev = cond.device(); + let mut saidas: Vec = Vec::with_capacity(self.n); + let cond_b = if cond.dims().len() == 1 { + cond.broadcast_as((1, self.d_cond))? + } else { + cond.clone() + }; + for t in &self.tentativas { + let o = t.fwd(&cond_b)?; + let o = if temp > 0.0 { + let total = self.d_saida; + let ruido: Vec = + (0..total).map(|_| rng.normal(0.0, temp as f64) as f32).collect(); + let rt = Tensor::from_vec(ruido, (1, self.d_saida), &dev)?; + (o.clone() + rt.affine(1.0, 0.0)?)? + } else { + o + }; + saidas.push(o); + } + Ok(Tensor::cat(&saidas, 0)?) // (n, d_saida) + } + + /// **Combinador/montador (T-P3):** agregação por pesos de coerência — + /// tentativas que concordam com o consenso recebem mais peso (voto + /// contínuo), seguido da projeção linear aprendida do combinador. + pub fn agregar(&self, candidatos: &Tensor) -> Result<(Tensor, Vec)> { + let consenso = candidatos.mean_keepdim(0)?; // (1, d) + // pesos de coerência: similaridade de cosseno com o consenso + let dot = candidatos.matmul(&consenso.transpose(0, 1)?)?; // (n, 1) + let nc = candidatos.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?; + let ncs = consenso.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?; + let cos = dot.broadcast_div(&nc.broadcast_mul(&ncs)?)?; // (n, 1) + let cos_v = cos.flatten_all()?.to_vec1::()?; + let exps: Vec = cos_v.iter().map(|c| (*c as f64 * 4.0).exp()).collect(); + let soma: f64 = exps.iter().sum(); + let pesos: Vec = exps.iter().map(|e| e / soma).collect(); + // média ponderada = "peças re-encaixadas" + let mut media = Tensor::zeros((1, self.d_saida), candle_core::DType::F32, candidatos.device())?; + for (i, p) in pesos.iter().enumerate() { + let linha = candidatos.get(i)?.broadcast_as((1, self.d_saida))?; + media = (media.affine(1.0, 0.0)? + linha.affine(*p, 0.0)?)?; + } + // montagem final pelo combinador aprendido + let concat = candidatos.flatten_all()?.broadcast_as((1, self.d_saida * self.n))?; + let montado = self.combinador.fwd(&concat)?; + // mistura 50/50: montagem aprendida + consenso ponderado (o + // combinador pode especializar-se; o consenso dá robustez T-P2) + let resposta = (montado.affine(0.5, 0.0)? + media.affine(0.5, 0.0)?)?; + Ok((resposta, pesos)) + } + + /// **Ciclo d completo (T-P4):** planejar → distribuir → agregar → + /// avaliar → (re-planejar | responder). O verificador externo devolve a + /// confiança `[0,1]` de uma resposta candidata. + pub fn raciocinar( + &mut self, + cond: &Tensor, + verificador: &dyn Fn(&Tensor) -> f64, + rng: &mut RngDeterministico, + ) -> Result { + let mut trajetoria = Vec::new(); + let mut telemetria: HashMap = HashMap::new(); + let mut temperatura = 0.1f64; + let mut melhor = (None::, -1.0f64, Vec::::new()); + let mut iteracoes = 0usize; + let mut candidatos_final = Tensor::zeros( + (self.n, self.d_saida), + candle_core::DType::F32, + cond.device(), + )?; + + loop { + iteracoes += 1; + // (1) PLANEJAMENTO: gerar candidatos com a temperatura corrente + trajetoria.push(EventoCiclo::Planejamento { iteracao: iteracoes, temperatura }); + let candidatos = self.candidatos(cond, temperatura, rng)?; + candidatos_final = candidatos.clone(); + // (2) DISTRIBUIÇÃO/DESMEMBRAMENTO DE COMPETÊNCIAS: pesos por + // coerência (quem concorda mais lidera a montagem) + let (resposta, pesos) = self.agregar(&candidatos)?; + trajetoria.push(EventoCiclo::DistribuicaoCompetencias { por_tentativa: pesos.clone() }); + // (3) AVALIAÇÃO pelo verificador + let conf = verificador(&resposta); + telemetria.insert(format!("conf_iter_{}", iteracoes), conf); + if conf > melhor.1 { + melhor = (Some(resposta), conf, pesos.clone()); + } + // (4) DECISÃO: responder ou re-planejar + if conf >= self.limiar_conf { + trajetoria.push(EventoCiclo::Resposta { confianca: conf, iteracoes }); + telemetria.insert("iteracoes".into(), iteracoes as f64); + telemetria.insert("confianca_final".into(), conf); + telemetria.insert("temperatura_final".into(), temperatura); + return Ok(ResultadoPensamento { + resposta: melhor.0, + candidatos: candidatos_final, + pesos: melhor.2, + confianca: conf, + iteracoes, + trajetoria, + telemetria, + }); + } + if iteracoes >= self.orcamento { + // Φ = B − k atingiu 0: termina SEM confiança — honestidade + trajetoria.push(EventoCiclo::Resposta { confianca: melhor.1, iteracoes }); + telemetria.insert("iteracoes".into(), iteracoes as f64); + telemetria.insert("confianca_final".into(), melhor.1); + telemetria.insert("temperatura_final".into(), temperatura); + telemetria.insert("orcamento_esgotado".into(), 1.0); + return Ok(ResultadoPensamento { + resposta: melhor.0, + candidatos: candidatos_final, + pesos: melhor.2, + confianca: melhor.1, + iteracoes, + trajetoria, + telemetria, + }); + } + // (5) RE-PLANEJAMENTO: aumenta a exploração (difusor mais quente) + temperatura = (temperatura * 1.6).min(2.0); + trajetoria.push(EventoCiclo::RePlanejamento { + motivo: format!("confiança {conf:.3} < limiar {}", self.limiar_conf), + }); + } + } + + /// **Perda de treino das tentativas + combinador (item a):** treino + /// cooperativo — cada tentativa minimiza sua perda contra o alvo E o + /// combinador minimiza a perda da agregação (o gradiente flui para + /// todos através do concat). + pub fn perda_treino(&self, cond: &Tensor, alvo: &Tensor) -> Result { + // cond (b, d_cond), alvo (b, d_saida) + let b = cond.dim(0)?; + let mut perdas: Vec = Vec::new(); + let mut saidas: Vec = Vec::with_capacity(self.n); + for t in &self.tentativas { + let o = t.fwd(cond)?; + let dif = (&o - alvo)?; + perdas.push((dif.sqr()?.sum_all()? / (self.d_saida as f64 * b as f64))?); + saidas.push(o); + } + let concat = Tensor::cat(&saidas, candle_core::D::Minus1)?; // (b, n·d) + let montado = self.combinador.fwd(&concat)?; + let dif_m = (&montado - alvo)?; + perdas.push((dif_m.sqr()?.sum_all()? / (self.d_saida as f64 * b as f64))?); + // soma das perdas (cooperação: todas as redes + montador) + let mut total = perdas[0].clone(); + for p in perdas.iter().skip(1) { + total = (total + p.clone())?; + } + Ok(total) + } + + /// **Integração com o mecanismo de atenção (T-P5):** anexa as saídas das + /// tentativas como chaves/valores extra com gate escalar. + /// + /// Com `gate = 0` (nascimento): saída IDÊNTICA à atenção base + /// (neutralidade); o gradiente flui pelo gate (não-degenerado). + pub fn atencao_integrada( + &self, + q: &Tensor, + k_base: &Tensor, + v_base: &Tensor, + candidatos: &Tensor, + ) -> Result { + // q (b, n_q, d), k_base (b, n_kv, d), v_base (b, n_kv, d_v), + // candidatos (n_tent, d) — projetados para d e d_v + let dev = q.device(); + let d = k_base.dim(candle_core::D::Minus1)?; + let d_v = v_base.dim(candle_core::D::Minus1)?; + let b = q.dim(0)?; + // projeta candidatos (média simples — sem parâmetros extra): os + // candidatos têm d_saida; se d == d_saida usa direto, senão padding/ + // truncamento honesto + let cand = if self.d_saida == d { + candidatos.clone() + } else if self.d_saida > d { + let v = candidatos + .flatten_all()? + .to_vec1::()? + .chunks(self.d_saida) + .map(|c| c[..d].to_vec()) + .flatten() + .collect::>(); + Tensor::from_vec(v, (self.n, d), &dev)? + } else { + let mut v = candidatos.flatten_all()?.to_vec1::()?; + let mut linhas = Vec::new(); + for i in 0..self.n { + let mut linha = v[i * self.d_saida..(i + 1) * self.d_saida].to_vec(); + linha.extend(std::iter::repeat(0f32).take(d - self.d_saida)); + linhas.push(linha); + } + v = linhas.into_iter().flatten().collect(); + Tensor::from_vec(v, (self.n, d), &dev)? + }; + let k_extra = cand.broadcast_as((b, self.n, d))?; + let v_extra = if d_v == d { + k_extra.clone() + } else { + // projeção simples por média de colunas quando d_v != d + let media = k_extra.mean_keepdim(candle_core::D::Minus1)?; + media.broadcast_as((b, self.n, d_v))? + }; + // atenção sobre [base; extra] + let k = Tensor::cat(&[k_base.clone(), k_extra], 1)?; + let v = Tensor::cat(&[v_base.clone(), v_extra], 1)?; + let scores = (q.matmul(&k.transpose(1, 2)?)? / (d as f64).sqrt())?; + let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1)?; + let y_base_e_extra = attn.matmul(&v)?; + // atenção apenas-base (para neutralidade: gate = 0 ⇒ saída = base) + let scores_base = (q.matmul(&k_base.transpose(1, 2)?)? / (d as f64).sqrt())?; + let attn_base = candle_nn::ops::softmax(&scores_base, candle_core::D::Minus1)?; + let y_base = attn_base.matmul(v_base)?; + // mistura: y = y_base + gate·(y_completo − y_base) + let g = self.gate_atencao.as_tensor(); + let delta = (&y_base_e_extra - &y_base)?; + let forma: Vec = delta.dims().to_vec(); + let delta = (delta.affine(1.0, 0.0)? * g.broadcast_as(forma)?)?; + Ok((&y_base + &delta)?) + } + + // ----------------------------------------------------------------------- + // v0.6.0 — verificador NCM (sem oráculo) + tentativas PARALELAS + // ----------------------------------------------------------------------- + + /// Candidatos das N tentativas em PARALELO (rayon) — as N redes têm + /// parâmetros disjuntos e entradas idênticas ⇒ N sub-DAGs independentes + /// no grafo de computação (mesma estrutura da prova M-R3): tempo + /// paralelo = max_i T_i + sobrecarga de agendamento. + /// Determinismo: o ruído da tentativa i usa a semente `semente + i` + /// (fluxos independentes por construção — reprodutível em qualquer + /// nº de threads). + pub fn candidatos_par( + &self, + cond: &Tensor, + temp: f64, + semente: u64, + ) -> Result<(Tensor, f64)> { + use rayon::prelude::*; + let dev = cond.device(); + let cond_b = if cond.dims().len() == 1 { + cond.broadcast_as((1, self.d_cond))? + } else { + cond.clone() + }; + let t0 = std::time::Instant::now(); + let saidas: Vec = self + .tentativas + .par_iter() + .enumerate() + .map(|(i, t)| -> Result { + let o = t.fwd(&cond_b)?; + if temp > 0.0 { + let mut rng = RngDeterministico::novo(semente + i as u64); + let ruido: Vec = + (0..self.d_saida).map(|_| rng.normal(0.0, temp) as f32).collect(); + let rt = Tensor::from_vec(ruido, (1, self.d_saida), &dev)?; + Ok((&o + &rt.affine(1.0, 0.0)?)?) + } else { + Ok(o) + } + }) + .collect::>>()?; + let dt = t0.elapsed().as_secs_f64(); + Ok((Tensor::cat(&saidas, 0)?, dt)) // (n, d_saida), tempo paralelo + } + + /// **Variante em LOTE (v0.6.0 — fase C):** cond (b, d_cond) → + /// candidatos (b, n, d_saida) — as N tentativas processam o lote + /// inteiro (paralelas entre si; ruído por (linha, tentativa) com + /// semente determinística `semente + i`). + pub fn candidatos_lote(&self, cond: &Tensor, temp: f64, semente: u64) -> Result { + let b = cond.dim(0)?; + let dev = cond.device(); + let mut saidas: Vec = Vec::with_capacity(self.n); + for (i, t) in self.tentativas.iter().enumerate() { + let o = t.fwd(cond)?; // (b, d_saida) + let o = if temp > 0.0 { + let mut rng = RngDeterministico::novo(semente + i as u64); + let ruido: Vec = (0..b * self.d_saida) + .map(|_| rng.normal(0.0, temp) as f32) + .collect(); + let rt = Tensor::from_vec(ruido, (b, self.d_saida), &dev)?; + (&o + &rt)? + } else { + o + }; + saidas.push(o.unsqueeze(1)?); // (b, 1, d_saida) + } + Ok(Tensor::cat(&saidas, 1)?) // (b, n, d_saida) + } + + /// **Agregação em LOTE (v0.6.0):** candidatos (b, n, d) → resposta + /// (b, d) — mesma mistura 50/50 (montagem aprendida + consenso + /// ponderado por coerência) da versão 1-D, VETORIZADA e totalmente + /// diferenciável (gradiente flui pelos DOIS caminhos). Devolve também + /// os pesos médios (telemetria). + pub fn agregar_lote(&self, candidatos: &Tensor) -> Result<(Tensor, Vec)> { + let b = candidatos.dim(0)?; + let n = candidatos.dim(1)?; + let d = candidatos.dim(2)?; + let dev = candidatos.device(); + // consenso (b, d) + let consenso = candidatos.mean(1)?; + // cosseno por (b, n): ⟨cand_i, consenso⟩ / (‖cand_i‖·‖consenso‖) + let dot = candidatos.matmul(&consenso.unsqueeze(2)?)?; // (b, n, 1) + let nc = candidatos.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?; // (b, n, 1) + let ncs = consenso.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?.unsqueeze(1)?; // (b, 1, 1) + let cos = dot.broadcast_div(&nc.broadcast_mul(&ncs)?)?; // (b, n, 1) + // pesos softmax de coerência (b, n, 1) — tensor puro (grafo intacto) + let exps = cos.affine(4.0, 0.0)?.exp()?; + let soma = exps.sum_keepdim(1)?; // (b, 1, 1) + let pesos = exps.broadcast_div(&soma)?; // (b, n, 1) + // média ponderada: Σ_n pesos·cand → (b, d) + let media = candidatos.broadcast_mul(&pesos)?.sum(1)?; // (b, d) + // montagem aprendida: concat sobre n → combinador + let concat = candidatos.reshape((b, n * d))?; + let montado = self.combinador.fwd(&concat)?; // (b, d) + let resposta = (montado.affine(0.5, 0.0)? + media.affine(0.5, 0.0)?)?; + // pesos médios para telemetria (leitura — não afeta o grafo) + let pesos_v = pesos.flatten_all()?.to_vec1::()?; + let mut pesos_medios = vec![0f64; n]; + for (k, p) in pesos_v.iter().enumerate() { + pesos_medios[k % n] += *p as f64 / b as f64; + } + let _ = d; + let _ = dev; + Ok((resposta, pesos_medios)) + } + + /// **Ciclo d com o VERIFICADOR NCM (v0.6.0 — fecha o débito r-21 §3.1).** + /// + /// `logits_fn` mapeia uma resposta candidata (1, d_saida) → logits da + /// distribuição preditiva do BACKBONE condicionada à correção (passada + /// corrente — V-4 causal). O verificador calibrado devolve a confiança; + /// NENHUM acesso ao alvo verdadeiro (o oráculo morre aqui). + pub fn raciocinar_ncm( + &mut self, + cond: &Tensor, + verificador: &crate::verificador::VerificadorNcm, + logits_fn: &dyn Fn(&Tensor) -> Result, + rng: &mut RngDeterministico, + ) -> Result { + let mut trajetoria = Vec::new(); + let mut telemetria: HashMap = HashMap::new(); + let mut temperatura = 0.1f64; + let mut melhor = (None::, -1.0f64, Vec::::new()); + let mut iteracoes = 0usize; + let mut candidatos_final = Tensor::zeros( + (self.n, self.d_saida), + candle_core::DType::F32, + cond.device(), + )?; + loop { + iteracoes += 1; + trajetoria.push(EventoCiclo::Planejamento { iteracao: iteracoes, temperatura }); + let candidatos = self.candidatos(cond, temperatura, rng)?; + candidatos_final = candidatos.clone(); + let (resposta, pesos) = self.agregar(&candidatos)?; + trajetoria.push(EventoCiclo::DistribuicaoCompetencias { por_tentativa: pesos.clone() }); + // AVALIAÇÃO pelo VERIFICADOR NCM (sem oráculo): confiança + // calibrada da distribuição preditiva do backbone + let logits = logits_fn(&resposta)?; + let conf = verificador.confianca_1d(&logits)?; + telemetria.insert(format!("conf_iter_{}", iteracoes), conf); + if conf > melhor.1 { + melhor = (Some(resposta), conf, pesos.clone()); + } + if conf >= self.limiar_conf { + trajetoria.push(EventoCiclo::Resposta { confianca: conf, iteracoes }); + telemetria.insert("iteracoes".into(), iteracoes as f64); + telemetria.insert("confianca_final".into(), conf); + telemetria.insert("temperatura_final".into(), temperatura); + telemetria.insert("verificador".into(), 1.0); // ncm (sem oráculo) + return Ok(ResultadoPensamento { + resposta: melhor.0, + candidatos: candidatos_final, + pesos: melhor.2, + confianca: conf, + iteracoes, + trajetoria, + telemetria, + }); + } + if iteracoes >= self.orcamento { + trajetoria.push(EventoCiclo::Resposta { confianca: melhor.1, iteracoes }); + telemetria.insert("iteracoes".into(), iteracoes as f64); + telemetria.insert("confianca_final".into(), melhor.1); + telemetria.insert("temperatura_final".into(), temperatura); + telemetria.insert("orcamento_esgotado".into(), 1.0); + telemetria.insert("verificador".into(), 1.0); + return Ok(ResultadoPensamento { + resposta: melhor.0, + candidatos: candidatos_final, + pesos: melhor.2, + confianca: melhor.1, + iteracoes, + trajetoria, + telemetria, + }); + } + temperatura = (temperatura * 1.6).min(2.0); + trajetoria.push(EventoCiclo::RePlanejamento { + motivo: format!("conf NCM {conf:.3} < limiar {}", self.limiar_conf), + }); + } + } +} diff --git a/src/pensamento/quebra_cabeca.rs b/src/pensamento/quebra_cabeca.rs new file mode 100644 index 0000000000000000000000000000000000000000..123dd9f36b7f6aab51d0c76a167295f077cca8f0 --- /dev/null +++ b/src/pensamento/quebra_cabeca.rs @@ -0,0 +1,233 @@ +//! `pensamento::quebra_cabeca` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +// --------------------------------------------------------------------------- +// QUEBRA-CABEÇA pós-treinamento (v0.7.0 — inovação com o conhecimento +// treinado; requisito do usuário; provas Q-1..Q-6 em r-31) +// --------------------------------------------------------------------------- + +/// Configuração do quebra-cabeça (r-28 §4.3). +#[derive(Clone, Copy, Debug)] +pub struct ConfigQuebraCabeca { + /// K peças em que o conhecimento é quebrado. + pub n_pecas: usize, + /// orçamento B de rodadas (terminação Q-4). + pub n_rodadas_max: usize, + /// limiar de validade (mesmo τ_c calibrado do verificador — Q-2). + pub tau_c: f64, + /// teto do ajuste por componente na árvore (regulação). + pub delta_max: f64, + /// remonta as peças com mistura ORTOGONAL por peça (rotação/ortogonalidade). + pub rotacao_ortogonal: bool, + /// para a execução após este nº de rodadas consecutivas sem aceite. + pub paciencia: usize, +} + +impl Default for ConfigQuebraCabeca { + fn default() -> Self { + ConfigQuebraCabeca { + n_pecas: 4, + n_rodadas_max: 12, + tau_c: 0.55, + delta_max: 0.05, + rotacao_ortogonal: true, + paciencia: 5, + } + } +} + +impl ConfigQuebraCabeca { + pub fn novo(n_pecas: usize, n_rodadas: usize, tau_c: f64) -> Self { + ConfigQuebraCabeca { + n_pecas, + n_rodadas_max: n_rodadas, + tau_c, + delta_max: 0.05, + rotacao_ortogonal: true, + paciencia: (n_rodadas / 2).max(2), + } + } +} + +/// Uma rodada do quebra-cabeça. +#[derive(Debug, Clone)] +pub struct RodadaQuebraCabeca { + pub rodada: usize, + /// permutação das peças aplicada na remontagem (reordenação). + pub permutacao_pecas: Vec, + /// confiança NCM do candidato remontado (reavaliação). + pub confianca: f64, + /// conf ≥ τ_c ⇒ VÁLIDO (ajusta a árvore); senão PUNIDO (descarta). + pub aceito: bool, + /// delta aplicado na árvore (0 quando descartado). + pub delta_aplicado: f64, + /// rotações ortogonais usadas (quando ativas) — por peça. + pub usou_rotacao: bool, +} + +/// Resultado consolidado do quebra-cabeça. +#[derive(Debug, Clone)] +pub struct ResultadoQuebraCabeca { + pub rodadas: Vec, + pub aceitos: usize, + pub rejeitados: usize, + pub confianca_base: f64, + pub confianca_melhor: f64, + pub telemetria: HashMap, +} + +/// **Quebra-cabeça de inovação pós-treinamento** (r-28 §4.3): +/// 1. quebra o conhecimento treinado `conceitos` (m, d) em K peças por +/// permutação aleatória das linhas (Fisher–Yates com RNG determinístico); +/// 2. remonta as peças em nova ordem (e com mistura ortogonal por peça +/// quando `rotacao_ortogonal`) — "descobrir novas possibilidades"; +/// 3. reavalia cada remontagem pelo verificador NCM (calibrado — V-2); +/// VÁLIDO (conf ≥ τ_c) ⇒ ajuste regulado na árvore de pesos indexada +/// (nó "pensamento", crédito fundo-topo); PUNIDO ⇒ descarta e o incremento +/// de punição sobe no nó (alimenta o gate do FNH — integração v0.7.0); +/// 4. repete até o orçamento B ou a paciência esgotar (Q-4). +/// +/// **Provas (r-31):** Q-1 cobertura (permutações uniformes ⇒ S_K no suporte; +/// rotações varrem O(d)); Q-2 soundness herdada (mesmo limiar calibrado do +/// verificador — P(aceite inválido) ≤ 1−τ_c+ECE); Q-3 potencial monotônico +/// (Φ_r = Σ conf dos aceitos é não-decrescente; rejeições não tocam Φ); +/// Q-4 terminação (B finito, rodadas O(m·d)); Q-5 invariantes da árvore +/// preservadas (ajustes só tocam métricas, nunca a estrutura — T-A1 intacto; +/// agregação pós-ajuste mantém T-A2); Q-6 causalidade (permutação e confiança +/// medidas antes do ajuste — filtração F_r). +pub fn quebra_cabeca_inovar( + conceitos: &Tensor, + verificador: &crate::verificador::VerificadorNcm, + logits_fn: &dyn Fn(&Tensor) -> Result, + arvore: &mut crate::arvore_pesos::ArvorePesos, + cfg: &ConfigQuebraCabeca, + rng: &mut RngDeterministico, +) -> Result { + let dev = conceitos.device().clone(); + let (m, d) = (conceitos.dims()[0], conceitos.dims()[1]); + let k = cfg.n_pecas.clamp(1, m); + let no_pens = arvore.no("pensamento").unwrap_or(0); + + // linha de base: confiança do conhecimento original (agregado médio) + let base_agg = conceitos.mean(0)?; + let conf_base = { + let lg = logits_fn(&base_agg)?; + verificador.confianca_1d(&lg)? + }; + + let mut rodadas = Vec::new(); + let mut aceitos = 0usize; + let mut rejeitados = 0usize; + let mut conf_melhor = conf_base; + let mut sem_aceite = 0usize; + + for r in 1..=cfg.n_rodadas_max { + if sem_aceite >= cfg.paciencia { + break; + } + // (1) QUEBRA: permutação de linhas Fisher–Yates (Q-6: amostrada ANTES) + let mut perm: Vec = (0..m).collect(); + for i in (1..m).rev() { + let j = rng.inteiro(i + 1); + perm.swap(i, j); + } + // fatia em K peças contíguas (tamanhos quase iguais) + let tamanho = (m + k - 1) / k; + let mut pecas = Vec::with_capacity(k); + let mut ini = 0usize; + while ini < m { + let fim = (ini + tamanho).min(m); + let idx: Vec = perm[ini..fim].iter().map(|&x| x as u32).collect(); + let pedaco = conceitos.index_select(&Tensor::from_vec(idx, (fim - ini,), &dev)?, 0)?; + pecas.push(pedaco); + ini = fim; + } + // (2) REMONTA: nova ordem aleatória das peças (permutação de peças) + let mut perm_pecas: Vec = (0..pecas.len()).collect(); + for i in (1..pecas.len()).rev() { + let j = rng.inteiro(i + 1); + perm_pecas.swap(i, j); + } + let mut remontada = Vec::with_capacity(pecas.len()); + for &pi in &perm_pecas { + let mut pedaco = pecas[pi].clone(); + if cfg.rotacao_ortogonal && d >= 2 { + // mistura ortogonal da peça: Q ∈ O(d) via polar de gaussiana + // do RNG determinístico (preserva normas — ‖xQ‖_F = ‖x‖_F) + let q = q_ortogonal_deterministica(d, rng, &dev)?; + pedaco = pedaco.matmul(&q)?; + } + remontada.push(pedaco); + } + let candidata = Tensor::cat(&remontada, 0)?; // (m, d) remontado + // (3) REAVALIAÇÃO pelo verificador NCM (Q-2: mesmo limiar calibrado) + let agg = candidata.mean(0)?; + let lg = logits_fn(&agg)?; + let conf = verificador.confianca_1d(&lg)?; + let aceito = conf >= cfg.tau_c; + let delta = if aceito { + // VÁLIDO: ajusta a árvore bidirecional (indexada, com teto) + let escalado = (conf - cfg.tau_c).min(cfg.delta_max); + arvore.aplicar_ajuste_indexado(no_pens, "confianca", escalado, cfg.delta_max) + } else { + // PUNIDO: descarta; a punição sobe no nó (sinal para o gate FNH) + rejeitados += 1; + sem_aceite += 1; + arvore.aplicar_ajuste_indexado(no_pens, "punicao", cfg.delta_max * 0.5, cfg.delta_max); + 0.0 + }; + if aceito { + aceitos += 1; + sem_aceite = 0; + conf_melhor = conf_melhor.max(conf); + } + rodadas.push(RodadaQuebraCabeca { + rodada: r, + permutacao_pecas: perm_pecas, + confianca: conf, + aceito, + delta_aplicado: delta, + usou_rotacao: cfg.rotacao_ortogonal, + }); + } + // crédito fundo-topo após todas as rodadas (T-A2) + arvore.agregar_fundo_topo("confianca"); + arvore.agregar_fundo_topo("punicao"); + + let n_rod = rodadas.len(); + let mut telemetria = HashMap::new(); + telemetria.insert("rodadas".into(), n_rod as f64); + telemetria.insert("aceitos".into(), aceitos as f64); + telemetria.insert("rejeitados".into(), rejeitados as f64); + telemetria.insert("confianca_base".into(), conf_base); + telemetria.insert("confianca_melhor".into(), conf_melhor); + telemetria.insert( + "taxa_aceitacao".into(), + if n_rod > 0 { aceitos as f64 / n_rod as f64 } else { 0.0 }, + ); + telemetria.insert("rotacao_ativa".into(), if cfg.rotacao_ortogonal { 1.0 } else { 0.0 }); + Ok(ResultadoQuebraCabeca { + rodadas, + aceitos, + rejeitados, + confianca_base: conf_base, + confianca_melhor: conf_melhor, + telemetria, + }) +} + +/// Q ∈ O(d) determinística: gaussiana do RNG próprio → retração polar. +/// Ortogonal EXATA (δ_ort ≤ 1e-6) e reprodutível (mesma semente ⇒ mesmo Q). +pub fn q_ortogonal_deterministica( + d: usize, + rng: &mut RngDeterministico, + dev: &Device, +) -> Result { + let n = d * d; + let v: Vec = (0..n).map(|_| rng.normal_padrao() as f32).collect(); + let g = Tensor::from_vec(v, (d, d), dev)?; + crate::stiefel::retrair_polar_frobenius(&g, 90) +} diff --git a/src/pensamento/testes.rs b/src/pensamento/testes.rs new file mode 100644 index 0000000000000000000000000000000000000000..16ce20b61cfb05fe7119a55bd4c68d49025138dc --- /dev/null +++ b/src/pensamento/testes.rs @@ -0,0 +1,303 @@ +//! `pensamento::testes` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + + +#[cfg(test)] +mod testes { + use super::*; + + #[test] + fn teste_confianca_extremos() { + // T-P1: Dirac ⇒ conf = 1; uniforme ⇒ conf = 0 + let dev = Device::Cpu; + let dirac = Tensor::from_vec(vec![100.0f32, 0.0, 0.0, 0.0], (1, 4), &dev).unwrap(); + let uniforme = Tensor::from_vec(vec![0.0f32; 4], (1, 4), &dev).unwrap(); + let c1 = confianca_de_logits(&dirac).unwrap(); + let c0 = confianca_de_logits(&uniforme).unwrap(); + assert!(c1 > 0.999, "conf dirac = {c1}"); + assert!(c0 < 0.001, "conf uniforme = {c0}"); + } + + #[test] + fn teste_tp3_projetar_ridge_minimiza() { + // T-P3: r* da projeção ridge tem resíduo ≤ qualquer perturbação + let dev = Device::Cpu; + let w = Tensor::randn(0f32, 1.0, (8, 3), &dev).unwrap(); + let r_alvo = Tensor::randn(0f32, 1.0, (8, 5), &dev).unwrap(); + let r = projetar_ridge(&w, &r_alvo, 1e-6).unwrap(); + let residuo = |rr: &Tensor| { + let d = (w.matmul(rr).unwrap() - &r_alvo).unwrap(); + d.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64 + }; + let f0 = residuo(&r); + let mut rng = RngDeterministico::novo(1); + for _ in 0..30 { + let pert: Vec = (0..15).map(|_| rng.normal(0.0, 0.1) as f32).collect(); + let rp = (Tensor::from_vec(pert, (3, 5), &dev).unwrap() + &r).unwrap(); + let fp = residuo(&rp); + assert!(f0 <= fp + 1e-6, "residuo r* = {f0} > perturbado {fp}"); + } + } + + #[test] + fn teste_tp4_terminacao_e_orcamento() { + // o ciclo d SEMPRE termina em ≤ orçamento iterações (potencial + // Φ = B − k); com verificador impossível, termina com + // orcamento_esgotado = 1. + let dev = Device::Cpu; + let mut motor = MotorPensamento::novo(6, 4, 3, 0.99, 5, &dev).unwrap(); + let cond = Tensor::randn(0f32, 1.0, (6,), &dev).unwrap(); + let mut rng = RngDeterministico::novo(2); + let res = motor + .raciocinar(&cond, &|_| 0.0, &mut rng) // verificador sempre 0 + .unwrap(); + assert!(res.iteracoes <= 5, "iterações = {} > orçamento", res.iteracoes); + assert_eq!(res.telemetria.get("orcamento_esgotado"), Some(&1.0)); + assert!(res.resposta.is_some(), "melhor candidato ainda é retornado"); + } + + #[test] + fn teste_tp5_neutralidade_no_nascimento() { + // gate = 0 ⇒ saída da atenção integrada é BIT-A-BIT a da base + let dev = Device::Cpu; + let motor = MotorPensamento::novo(8, 8, 3, 0.5, 3, &dev).unwrap(); + let q = Tensor::randn(0f32, 1.0, (1, 4, 8), &dev).unwrap(); + let k = Tensor::randn(0f32, 1.0, (1, 6, 8), &dev).unwrap(); + let v = Tensor::randn(0f32, 1.0, (1, 6, 8), &dev).unwrap(); + let cand = Tensor::randn(0f32, 1.0, (3, 8), &dev).unwrap(); + let y_int = motor.atencao_integrada(&q, &k, &v, &cand).unwrap(); + // atenção base pura + let scores = (q.matmul(&k.transpose(1, 2).unwrap()).unwrap() / (8f64).sqrt()).unwrap(); + let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1).unwrap(); + let y_base = attn.matmul(&v).unwrap(); + let dif = (&y_int - &y_base).unwrap().abs().unwrap().max_all().unwrap().to_scalar::().unwrap(); + assert!(dif == 0.0, "gate=0 deveria dar bit-a-bit, dif = {dif}"); + } + + #[test] + fn teste_treino_cooperativo_e_agregacao() { + // item a: as tentativas + combinador APRENDEM a função alvo; a + // agregação converge para o alvo melhor que uma tentativa isolada + // no pior caso (verificação numérica honesta em escala pequena). + let dev = Device::Cpu; + let mut motor = MotorPensamento::novo(6, 4, 3, 0.5, 3, &dev).unwrap(); + let vars = motor.vars(); + let mut otim = crate::treino::AdamWManual::novo(vars, 5e-3, 0.0); + let mut rng = RngDeterministico::novo(4); + // alvo: função não-linear determinística de cond + let dados: Vec<(Vec, Vec)> = (0..256) + .map(|_| { + let c: Vec = (0..6).map(|_| rng.normal(0.0, 1.0) as f32).collect(); + let a: Vec = (0..4) + .map(|j| { + (c[j % 6] * 0.5 + (c[(j + 1) % 6] * c[(j + 2) % 6]) * 0.3 + 0.1) as f32 + }) + .collect(); + (c, a) + }) + .collect(); + let cs: Vec = dados.iter().flat_map(|(c, _)| c.clone()).collect(); + let as_: Vec = dados.iter().flat_map(|(_, a)| a.clone()).collect(); + let cond = Tensor::from_vec(cs, (256, 6), &dev).unwrap(); + let alvo = Tensor::from_vec(as_, (256, 4), &dev).unwrap(); + let mut perda0 = f64::NAN; + let mut perda1 = f64::NAN; + for passo in 0..200 { + let perda = motor.perda_treino(&cond, &alvo).unwrap(); + if passo == 0 { + perda0 = perda.to_scalar::().unwrap() as f64; + } + perda1 = perda.to_scalar::().unwrap() as f64; + otim.passo(crate::treino::grads_achatados(&perda, &motor.vars()).unwrap()); + } + assert!(perda1 < perda0 * 0.5, "treino cooperativo: {perda0} → {perda1}"); + // agregação: verificador = negativo do erro quadrático médio + let mut rng2 = RngDeterministico::novo(9); + let cand = motor.candidatos(&cond.get(0).unwrap(), 0.0, &mut rng2).unwrap(); + let (resp, pesos) = motor.agregar(&cand).unwrap(); + assert_eq!(resp.dims().to_vec(), vec![1, 4]); + let soma: f64 = pesos.iter().sum(); + assert!((soma - 1.0).abs() < 1e-9, "pesos somam {soma}"); + assert!(pesos.iter().all(|&p| p >= 0.0)); + } + + #[test] + fn teste_tp2_auto_consistencia_numérica() { + // T-P2 numérico: N tentativas com viés comum pequeno; a média + // agregada tem erro menor que a tentativa mediana (redução de + // variância) na maioria das repetições. + let dev = Device::Cpu; + let mut rng = RngDeterministico::novo(6); + let alvo = [1.0f64, 2.0, -1.0, 0.5]; + let mut venceu = 0; + let reps = 200; + for _ in 0..reps { + let n = 7; + let erros: Vec = (0..n) + .map(|_| { + alvo + .iter() + .map(|&a| { + let ruido = rng.normal(0.0, 1.0); + (a + ruido - a).abs() + }) + .sum::() + }) + .collect(); + let media: Vec = (0..4) + .map(|j| { + (0..n).map(|i| alvo[j] + rng.normal(0.0, 1.0)).sum::() / n as f64 + }) + .collect(); + let erro_media: f64 = media.iter().zip(alvo.iter()).map(|(m, a)| (m - a).abs()).sum(); + let mut ordenado = erros.clone(); + ordenado.sort_by(|a, b| a.partial_cmp(b).unwrap()); + let mediana = ordenado[n / 2]; + if erro_media <= mediana { + venceu += 1; + } + } + // a média vence a mediana na grande maioria dos casos (E[erro da + // média] = σ/√n < σ = E[erro de UMA tentativa]) + assert!( + venceu as f64 / reps as f64 >= 0.6, + "auto-consistência venceu só {}/{}", + venceu, + reps + ); + } + + // ------------------------------------------------------------------ + // Quebra-cabeça (v0.7.0) — gates Q-1..Q-6 (provas r-31) + // ------------------------------------------------------------------ + + fn cenario_quebra_cabeca() + -> (Tensor, crate::verificador::VerificadorNcm, crate::arvore_pesos::ArvorePesos) { + let dev = Device::Cpu; + let mut rng = RngDeterministico::novo(777); + // "conhecimento treinado": 12 conceitos (d=8) com estrutura + let m = 12usize; + let d = 8usize; + let v: Vec = (0..m * d).map(|i| rng.normal_padrao() as f32).collect(); + let conceitos = Tensor::from_vec(v, (m, d), &dev).unwrap(); + // verificador calibrado com um "verificador" sintético: logits + // favoráveis ao agregado médio dos conceitos + let mut verif = crate::verificador::VerificadorNcm::novo(); + let _ = &mut verif; + let mut arvore = crate::arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new()); + let _ = &mut arvore; + (conceitos, verif, arvore) + } + + fn logits_fn_exemplo(x: &Tensor) -> Result { + // logits determinísticos correlacionados com a norma do agregado — + // quanto mais "concentrado" o agregado, maior a confiança + let norma = x.sqr()?.sum_all()?.to_scalar::()? as f64; + let base = (norma * 2.0).min(8.0); + let v: Vec = vec![base as f32, 0.5, 0.2, 0.1]; + Ok(Tensor::from_vec(v, (4,), x.device())?) + } + + /// Q-2/Q-3: todo ACEITO tem conf ≥ τ_c; rejeições não aplicam delta; + /// potencial monotônico (confiança acumulada só cresce nos aceites). + #[test] + fn quebra_cabeca_soundness_e_potencial() -> Result<()> { + let (conceitos, verif, mut arvore) = cenario_quebra_cabeca(); + let cfg = ConfigQuebraCabeca::novo(4, 10, 0.55); + let mut rng = RngDeterministico::novo(4242); + let r = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?; + for rod in &r.rodadas { + if rod.aceito { + assert!( + rod.confianca >= cfg.tau_c, + "Q-2: aceito com conf {} < τ_c {}", + rod.confianca, + cfg.tau_c + ); + assert!(rod.delta_aplicado > 0.0, "aceite aplica delta > 0"); + } else { + assert_eq!(rod.delta_aplicado, 0.0, "Q-3: rejeitado não aplica delta"); + } + } + // potencial monotônico: Σ conf dos aceitos ≤ Σ max(conf, τ_c) das rodadas + let pot: f64 = r.rodadas.iter().filter(|x| x.aceito).map(|x| x.confianca).sum(); + assert!(pot >= 0.0); + // Q-5: invariantes da árvore preservadas + arvore.invariantes_ok().expect("Q-5: invariantes T-A1 preservadas"); + Ok(()) + } + + /// Q-4: terminação — nunca excede n_rodadas_max nem roda sem parar. + #[test] + fn quebra_cabeca_termina() -> Result<()> { + let (conceitos, verif, mut arvore) = cenario_quebra_cabeca(); + let cfg = ConfigQuebraCabeca::novo(3, 6, 0.99); // τ inalcançável + let mut rng = RngDeterministico::novo(1); + let r = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?; + assert!(r.rodadas.len() <= 6, "Q-4: orçamento respeitado"); + assert_eq!(r.aceitos, 0, "τ inalcançável ⇒ nenhum aceite"); + assert!(r.rejeitados > 0); + Ok(()) + } + + /// Q-4b: rejeições incrementam a PUNIÇÃO no nó pensamento (sinal FNH). + #[test] + fn quebra_cabeca_punicao_sobe() -> Result<()> { + let (conceitos, verif, mut arvore) = cenario_quebra_cabeca(); + let no_pens = arvore.no("pensamento").unwrap(); + let antes = arvore.nos[no_pens].metricas.get("punicao").copied().unwrap_or(0.0); + let cfg = ConfigQuebraCabeca::novo(3, 5, 0.999); + let mut rng = RngDeterministico::novo(9); + let _ = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?; + let depois = arvore.nos[no_pens].metricas.get("punicao").copied().unwrap_or(0.0); + assert!(depois > antes, "punição {depois} deve > {antes} após rejeições"); + Ok(()) + } + + /// Rotação/ortogonalidade: ‖xQ‖_F = ‖x‖_F (preserva energia) e a + /// remontagem com rotação difere da sem rotação (exploração real). + #[test] + fn quebra_cabeca_rotacao_preserva_norma() -> Result<()> { + let dev = Device::Cpu; + let mut rng = RngDeterministico::novo(31); + let v: Vec = (0..5 * 6).map(|_| rng.normal_padrao() as f32).collect(); + let x = Tensor::from_vec(v, (5, 6), &dev)?; + let q = q_ortogonal_deterministica(6, &mut rng, &dev)?; + let xq = x.matmul(&q)?; + let nx = x.sqr()?.sum_all()?.to_scalar::()?; + let nxq = xq.sqr()?.sum_all()?.to_scalar::()?; + assert!( + (nx - nxq).abs() / nx < 1e-4, + "‖xQ‖_F = {nxq} deve ≈ ‖x‖_F = {nx} (ortogonalidade)" + ); + // Q é ortogonal exato + let delta = crate::stiefel::erro_ortogonalidade(&q)?; + assert!(delta < 1e-6, "δ_ort(Q) = {delta}"); + // remontagem COM rotação difere da SEM rotação + let d2 = (&xq - &x)?.abs()?.max_all()?.to_scalar::()?; + assert!(d2 > 1e-3, "rotação deve explorar (dif = {d2})"); + Ok(()) + } + + /// Determinismo: mesma semente ⇒ mesmas rodadas (reprodutibilidade). + #[test] + fn quebra_cabeca_deterministico() -> Result<()> { + let (c1, v1, mut a1) = cenario_quebra_cabeca(); + let (_, v2, mut a2) = cenario_quebra_cabeca(); + let cfg = ConfigQuebraCabeca::novo(4, 8, 0.5); + let mut r1 = RngDeterministico::novo(123); + let mut r2 = RngDeterministico::novo(123); + let q1 = quebra_cabeca_inovar(&c1, &v1, &logits_fn_exemplo, &mut a1, &cfg, &mut r1)?; + let q2 = quebra_cabeca_inovar(&c1, &v2, &logits_fn_exemplo, &mut a2, &cfg, &mut r2)?; + assert_eq!(q1.rodadas.len(), q2.rodadas.len()); + for (x, y) in q1.rodadas.iter().zip(q2.rodadas.iter()) { + assert_eq!(x.permutacao_pecas, y.permutacao_pecas, "mesma semente ⇒ mesma remontagem"); + assert!((x.confianca - y.confianca).abs() < 1e-12); + assert_eq!(x.aceito, y.aceito); + } + Ok(()) + } +} diff --git a/src/python.rs b/src/python.rs deleted file mode 100644 index d20c540c0ff91e089ba620f779d71a8b8d4f97e6..0000000000000000000000000000000000000000 --- a/src/python.rs +++ /dev/null @@ -1,2370 +0,0 @@ -//! FFI Python (PyO3) do KHTST_RUST — todo o `#[pymodule]`, os -//! `#[pyfunction]`/`#[pyclass]` e a cola Python (conversões, -//! callbacks, demos) vivem AQUI desde a v0.9.0 (antes: lib.rs, -//! ~105 kB — crítica "God Object" do agente Qwen3.8, r-34 §5). -//! A lógica de domínio (src/*.rs) não importa pyo3: `cargo test -//! --no-default-features` executa a suíte SEM linkar o Python. -//! -//! Regra da casa mantida: nenhuma funcionalidade da superfície -//! Python foi removida — os mesmos símbolos são registrados no -//! `nucleo_rust`. - -use pyo3::prelude::*; - -use crate::*; - - -/// Adaptador FFI→domínio (v0.9.0): embrulha um callable Python no tipo de -/// domínio `treino::CallbackTreino` — `treino.rs` não importa pyo3. -fn callback_treino_de_py(callback: Option>) -> treino::CallbackTreino { - use std::sync::Arc; - match callback { - None => None, - Some(c) => { - let f: pyo3::Py = c.unbind(); - let g: Arc = - Arc::new(move |dados: &serde_json::Value| { - pyo3::Python::with_gil(|py| { - if let Ok(obj) = pythonize::pythonize(py, dados) { - let _ = f.call1(py, (obj,)); - } - }); - }); - Some(g) - } - } -} - -#[pyfunction] -fn versao() -> &'static str { - "khtst-rust-0.9.0" -} - -#[pyfunction] -fn info_build() -> String { - format!( - "rustc={}; perfil={}; features={}; alvo={}", - build_info::RUSTC_VERSION, - build_info::PROFILE, - build_info::FEATURES_STR, - build_info::TARGET, - ) -} - -/// Lint de config com sugestões por Levenshtein (diagnóstico de digitação). -#[pyfunction] -fn lint_config(py: Python, caminho: &str) -> PyResult { - let txt = std::fs::read_to_string(caminho) - .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{caminho}: {e}")))?; - let v: serde_json::Value = serde_json::from_str(&txt) - .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?; - let achatado = diagnostico::achatar(&v); - let avisos = diagnostico::lint_chaves(&achatado); - let saida: Vec = avisos - .iter() - .map(|a| serde_json::json!({ - "chave": a.chave, "sugestao": a.sugestao, "causa": a.causa, "correcao": a.correcao, - })) - .collect(); - Ok(pythonize::pythonize(py, &serde_json::json!(saida))?.into()) -} - -#[pyclass(name = "Bpe")] -struct BpePy { - bpe: tokens::Bpe, -} - -#[pymethods] -impl BpePy { - #[staticmethod] - fn treinar(textos: Vec, vocab_alvo: usize) -> PyResult { - Ok(BpePy { bpe: tokens::Bpe::treinar(&textos, vocab_alvo) }) - } - - #[staticmethod] - fn carregar(caminho: &str) -> PyResult { - let txt = std::fs::read_to_string(caminho) - .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{e}")))?; - let v: serde_json::Value = serde_json::from_str(&txt) - .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?; - let bpe = tokens::Bpe::de_json(&v) - .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("formato BPE inválido"))?; - Ok(BpePy { bpe }) - } - - fn codificar(&mut self, texto: &str) -> Vec { - self.bpe.codificar(texto) - } - - fn decodificar(&self, ids: Vec) -> String { - self.bpe.decodificar(&ids) - } - - fn salvar(&self, caminho: &str) -> PyResult<()> { - std::fs::write(caminho, serde_json::to_string(&self.bpe.para_json()).unwrap()) - .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{e}")))?; - Ok(()) - } - - #[getter] - fn n_vocab(&self) -> usize { - self.bpe.n_vocab() - } -} - -#[pyclass(name = "Config")] -struct ConfigPy { - cfg: config::ConfigKhtst, - caminho: Option, -} - -#[pymethods] -impl ConfigPy { - #[new] - #[allow(never_type_fallback)] - #[pyo3(signature = (caminho=None))] - fn new(caminho: Option) -> PyResult { - let cfg = match &caminho { - Some(c) => { - let txt = std::fs::read_to_string(c) - .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{c}: {e}")))?; - let v: serde_json::Value = serde_json::from_str(&txt) - .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?; - serde_json::from_value(v) - .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("config: {e}")))? - } - None => config::ConfigKhtst::default(), - }; - cfg.validar().map_err(pyo3::exceptions::PyValueError::new_err)?; - Ok(ConfigPy { cfg, caminho }) - } - - fn validar(&self) -> PyResult<()> { - self.cfg.validar().map_err(pyo3::exceptions::PyValueError::new_err) - } - - fn checar_orcamento_cache(&self) -> PyResult { - self.cfg.checar_orcamento_cache().map_err(pyo3::exceptions::PyValueError::new_err) - } - - fn resumo(&self) -> String { - format!( - "modelo(vocab={}, d={}, camadas={}, ctx={}) ncm(L={}, niveis={:?}, K={}, d_lat={}, modo={}, ativo={})", - self.cfg.modelo.vocab, self.cfg.modelo.d_modelo, self.cfg.modelo.n_camadas, - self.cfg.modelo.comprimento_ctx, self.cfg.l_niveis(), self.cfg.ncm.niveis, - self.cfg.ncm.k_por_nivel, self.cfg.ncm.d_lat, self.cfg.ncm.modo_quantizacao, self.cfg.ncm.ativo, - ) - } - - #[getter] - fn l_niveis(&self) -> usize { - self.cfg.l_niveis() - } -} - -#[pyclass(name = "PonteDupla")] -struct PonteDuplaPy { - ponte: ponte::PonteDupla, -} - -#[pymethods] -impl PonteDuplaPy { - #[new] - #[pyo3(signature = (capacidade=64, timeout_ms=2000))] - fn new(capacidade: usize, timeout_ms: u64) -> Self { - PonteDuplaPy { ponte: ponte::PonteDupla::nova(capacidade, timeout_ms) } - } - - fn rodada(&self, carga_a: Vec, carga_b: Vec, nivel: usize) -> PyResult<(Vec, Vec)> { - let (ma, mb) = self.ponte.rodada(carga_a, carga_b, nivel) - .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; - Ok((ma.carga, mb.carga)) - } - - fn demo_professor_aluno(&self, rodadas: usize, dim: usize) -> PyResult { - let inicio: Vec = (0..dim).map(|i| (i as f32) * 0.01).collect(); - let stats = ponte::demo_professor_aluno(&self.ponte, rodadas, inicio, &|a, _| { - a.iter().map(|x| x * 1.05).collect() - }) - .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; - Python::with_gil(|py| { - Ok(pythonize::pythonize(py, &serde_json::to_value(&stats).unwrap())?.into()) - }) - } - - fn estatisticas(&self) -> PyResult { - let s = self.ponte.estatisticas(); - Python::with_gil(|py| { - Ok(pythonize::pythonize(py, &serde_json::to_value(&s).unwrap())?.into()) - }) - } -} - -#[cfg(feature = "ponte_async")] -#[pyclass(name = "PonteAsync")] -struct PonteAsyncPy { - interna: std::sync::Arc, -} - -#[cfg(feature = "ponte_async")] -#[pymethods] -impl PonteAsyncPy { - #[new] - #[pyo3(signature = (capacidade=64, timeout_ms=2000))] - fn new(capacidade: usize, timeout_ms: u64) -> Self { - PonteAsyncPy { interna: std::sync::Arc::new(ponte::assincrona::PonteAsync::nova(capacidade, timeout_ms)) } - } - - /// Corrotina: `await ponte.trocar(a, b, nivel)` — duplex simultânea. - fn trocar<'p>(&self, py: Python<'p>, carga_a: Vec, carga_b: Vec, nivel: usize) -> PyResult> { - let p = self.interna.clone(); - pyo3_async_runtimes::tokio::future_into_py(py, async move { - let (ma, mb) = p.rodada_async(carga_a, carga_b, nivel) - .await - .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; - Ok::<_, PyErr>((ma.carga, mb.carga)) - }) - } -} - -/// Treina a fase A (tokenizador). Devolve dict com resumo + telemetria. -#[pyfunction] -#[pyo3(signature = (cfg, sequencias, caminho_telemetria=None, caminho_checkpoint=None, callback=None, teto_seg=None))] -fn treinar_fase_a( - cfg: &ConfigPy, - sequencias: Vec>, - caminho_telemetria: Option, - caminho_checkpoint: Option, - callback: Option>, - teto_seg: Option, -) -> PyResult { - let cb = callback_treino_de_py(callback); - let mut hub = telemetria::TelemetryHub::novo(caminho_telemetria); - let dados = treino::DadosTreino::novo(sequencias, cfg.cfg.modelo.comprimento_ctx); - let mut resumo = treino::treinar_fase_a(&cfg.cfg, &dados, &mut hub, &cb, caminho_checkpoint.as_deref(), teto_seg) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fase A: {e}")))?; - let _ = &mut resumo; - Python::with_gil(|py| { - let dados = serde_json::json!({ - "resumo": { - "passos": resumo.passos, "perda_inicial": resumo.perda_inicial, - "perda_final": resumo.perda_final, "acc_recon": resumo.acc_recon, - "delta_ort_max": resumo.delta_ort_max, - "perplexidade_codebook": resumo.perplexidade_codebook, - "monotonia_prefixo": resumo.monotonia_prefixo, - "escore_aprendizado": resumo.escore_aprendizado, - }, - "telemetria": hub.dump(), - }); - Ok(pythonize::pythonize(py, &dados)?.into()) - }) -} - -/// Treina a fase B (gerador) com tokenizador congelado carregado de checkpoint. -#[pyfunction] -#[pyo3(signature = (cfg, sequencias, caminho_tokenizador, caminho_telemetria=None, caminho_checkpoint=None, callback=None, teto_seg=None))] -fn treinar_fase_b( - cfg: &ConfigPy, - sequencias: Vec>, - caminho_tokenizador: &str, - caminho_telemetria: Option, - caminho_checkpoint: Option, - callback: Option>, - teto_seg: Option, -) -> PyResult { - let cb = callback_treino_de_py(callback); - let mut hub = telemetria::TelemetryHub::novo(caminho_telemetria); - let dados = treino::DadosTreino::novo(sequencias, cfg.cfg.modelo.comprimento_ctx); - // carrega o tokenizador congelado - let dev = candle_core::Device::Cpu; - let mut tok = tokenizador::TokenizadorConceitos::novo(&cfg.cfg, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("construção: {e}")))?; - carregar_tokenizador(&mut tok, caminho_tokenizador) - .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; - let resumo = treino::treinar_fase_b(&cfg.cfg, &tok, &dados, &mut hub, &cb, caminho_checkpoint.as_deref(), teto_seg) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fase B: {e}")))?; - Python::with_gil(|py| { - let dados = serde_json::json!({ - "resumo": { - "passos": resumo.passos, "perda_inicial": resumo.perda_inicial, - "perda_final": resumo.perda_final, "acc_conceitos": resumo.acc_recon, - "escore_aprendizado": resumo.escore_aprendizado, - }, - "telemetria": hub.dump(), - }); - Ok(pythonize::pythonize(py, &dados)?.into()) - }) -} - -fn carregar_tokenizador(tok: &mut tokenizador::TokenizadorConceitos, caminho: &str) -> Result<(), String> { - let txt = std::fs::read_to_string(caminho).map_err(|e| format!("{e}"))?; - let v: serde_json::Value = serde_json::from_str(&txt).map_err(|e| format!("{e}"))?; - let obj = v.as_object().ok_or("formato de checkpoint inválido")?; - let vars = tok.vars(); - for (i, var) in vars.iter().enumerate() { - if let Some(entrada) = obj.get(&format!("v{i}")) { - let forma: Vec = entrada["forma"].as_array().map(|a| a.iter().filter_map(|x| x.as_u64().map(|y| y as usize)).collect()).unwrap_or_default(); - let dados: Vec = entrada["dados"].as_array().map(|a| a.iter().filter_map(|x| x.as_f64().map(|y| y as f32)).collect()).unwrap_or_default(); - if forma.is_empty() || dados.is_empty() { - continue; - } - let dev = candle_core::Device::Cpu; - let t2 = candle_core::Tensor::from_vec(dados, forma.clone(), &dev) - .and_then(|t| t.reshape(forma)) - .map_err(|e| format!("{e}"))?; - var.set(&t2).map_err(|e| format!("{e}"))?; - } - } - Ok(()) -} - -/// Gera amostras com NFE = L e devolve textos decodificados (modo conceitos). -#[pyfunction] -#[pyo3(signature = (cfg, caminho_tokenizador, caminho_gerador, n_amostras=2, parar_no_nivel=None, greedy=true))] -fn gerar_amostras( - cfg: &ConfigPy, - caminho_tokenizador: &str, - caminho_gerador: &str, - n_amostras: usize, - parar_no_nivel: Option, - greedy: bool, -) -> PyResult { - let dev = candle_core::Device::Cpu; - let mut tok = tokenizador::TokenizadorConceitos::novo(&cfg.cfg, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - carregar_tokenizador(&mut tok, caminho_tokenizador) - .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; - let mut ger = gerador::GeradorConceitos::novo(&cfg.cfg, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - carregar_gerador(&mut ger, caminho_gerador) - .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; - let mut amostrador = amostrador::AmostradorConceitos::novo( - &mut ger, &tok, cfg.cfg.ncm.cfg_scale, cfg.cfg.ncm.temperaturas.limites, cfg.cfg.ncm.temperaturas.entropia_alvo, - ); - let mut rng = ::seed_from_u64(4242); - let inicio = std::time::Instant::now(); - let (codigos, nfe) = amostrador.gerar(n_amostras.max(1), &dev, &mut rng, parar_no_nivel, greedy) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("amostragem: {e}")))?; - let tempo_ms = inicio.elapsed().as_secs_f64() * 1000.0; - // decodifica: ẑ final → decoder AR greedy → tokens - let nivel_final = codigos.len(); - let zhats = tok.reconstruir_de_codigos(&codigos, nivel_final, n_amostras.max(1), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let textos_ids = decodificar_textos(&tok, zhats.last(), n_amostras.max(1)) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - Python::with_gil(|py| { - let d = serde_json::json!({ - "nfe": nfe, "l": cfg.cfg.l_niveis(), "tempo_ms": tempo_ms, - "niveis_usados": nivel_final, "ids_tokens": textos_ids, - "temperaturas": amostrador.temperaturas, - }); - Ok(pythonize::pythonize(py, &d)?.into()) - }) -} - -fn decodificar_textos(tok: &tokenizador::TokenizadorConceitos, zhat: Option<&candle_core::Tensor>, b: usize) -> Result>, String> { - let Some(z) = zhat else { return Ok(vec![vec![]; b]) }; - tok.decodificar_livre(z, 48).map_err(|e| format!("{e}")) -} - -fn carregar_gerador(ger: &mut gerador::GeradorConceitos, caminho: &str) -> Result<(), String> { - let txt = std::fs::read_to_string(caminho).map_err(|e| format!("{e}"))?; - let v: serde_json::Value = serde_json::from_str(&txt).map_err(|e| format!("{e}"))?; - let obj = v.as_object().ok_or("formato inválido")?; - let vars = ger.vars(); - for (i, var) in vars.iter().enumerate() { - if let Some(entrada) = obj.get(&format!("v{i}")) { - let forma: Vec = entrada["forma"].as_array().map(|a| a.iter().filter_map(|x| x.as_u64().map(|y| y as usize)).collect()).unwrap_or_default(); - let dados: Vec = entrada["dados"].as_array().map(|a| a.iter().filter_map(|x| x.as_f64().map(|y| y as f32)).collect()).unwrap_or_default(); - if forma.is_empty() || dados.is_empty() { - continue; - } - let dev = candle_core::Device::Cpu; - let t = candle_core::Tensor::from_vec(dados, forma.clone(), &dev) - .and_then(|t| t.reshape(forma)) - .map_err(|e| format!("{e}"))?; - var.set(&t).map_err(|e| format!("{e}"))?; - } - } - Ok(()) -} - -/// Utilitários de prova expostos ao Python (V-série da suíte). -#[pyfunction] -fn verificar_mascara(cfg: &ConfigPy) -> PyResult { - let dev = candle_core::Device::Cpu; - let m = mascaras::mascara_block_causal(&cfg.cfg.ncm.niveis, gerador::N_PREFIXO, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - mascaras::contar_vazamentos(&cfg.cfg.ncm.niveis, gerador::N_PREFIXO, &m) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))) -} - -#[pyfunction] -fn verificar_stiefel(k: usize, d: usize) -> PyResult { - let dev = candle_core::Device::Cpu; - let c = stiefel::init_stiefel(k, d, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - stiefel::erro_ortogonalidade(&c.transpose(0, 1).unwrap()).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))) -} - -// =========================================================================== -// v0.5.0 — suíte SOM 4D, suíte JEPA, Pensamento, telemetria robusta -// =========================================================================== - -fn tensor_de_2d(dados: Vec>, dev: &candle_core::Device) -> candle_core::Result { - let d = dados.first().map(|r| r.len()).unwrap_or(1).max(1); - let n = dados.len(); - let flat: Vec = dados.into_iter().flat_map(|r| r.into_iter().map(|x| x as f32).collect::>()).collect(); - candle_core::Tensor::from_vec(flat, (n, d), dev) -} - -/// Treina um 4D-SOM e devolve telemetria completa (EQ/TE/massa/cobertura). -#[pyfunction] -fn treinar_som4d(py: Python, dados: Vec>, cfg: Option) -> PyResult { - let dev = candle_core::Device::Cpu; - let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut c = som4d::CfgSom4D::default(); - if let Some(js) = &cfg { - if let Ok(v) = serde_json::from_str::(js) { - if let Some(o) = v.as_object() { - for (k, val) in o { - match k.as_str() { - "nx" => c.nx = val.as_u64().unwrap_or(4) as usize, - "ny" => c.ny = val.as_u64().unwrap_or(4) as usize, - "nz" => c.nz = val.as_u64().unwrap_or(4) as usize, - "nt" => c.nt = val.as_u64().unwrap_or(4) as usize, - "epocas" => c.epocas = val.as_u64().unwrap_or(50) as usize, - "sigma0" => c.sigma0 = val.as_f64().unwrap_or(2.0), - "sigma1" => c.sigma1 = val.as_f64().unwrap_or(0.5), - "eta0" => c.eta0 = val.as_f64().unwrap_or(0.5), - "eta1" => c.eta1 = val.as_f64().unwrap_or(0.05), - "semente" => c.semente = val.as_u64().unwrap_or(42), - "espalhada" => { - c.inicializacao = if val.as_bool().unwrap_or(false) { - som4d::Inicializacao4D::Espalhada - } else { - som4d::Inicializacao4D::Dados - } - } - _ => {} - } - } - } - } - } - let som = som4d::treinar_som4d(&t, &c, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let d = serde_json::json!({ - "versao": som4d::versao(), - "celulas": som.n_celulas(), - "grade": [som.nx, som.ny, som.nz, som.nt], - "eq_inicial": som.eq_inicial, - "eq_final": som.eq_final, - "te_final": som.te_final, - "historico_eq_te": som.historia, - "massa_soma": som.massa.iter().sum::(), - }); - Ok(pythonize::pythonize(py, &d)?.into()) -} - -/// Treina um GNG e devolve telemetria (vértices, arestas, componentes, EQ). -#[pyfunction] -fn treinar_gng(py: Python, dados: Vec>, cfg: Option) -> PyResult { - let dev = candle_core::Device::Cpu; - let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut c = som4d::CfgGng::default(); - if let Some(js) = &cfg { - if let Ok(v) = serde_json::from_str::(js) { - if let Some(o) = v.as_object() { - for (k, val) in o { - match k.as_str() { - "passos" => c.passos = val.as_u64().unwrap_or(4000) as usize, - "max_vertices" => c.max_vertices = val.as_u64().unwrap_or(60) as usize, - "lambda" => c.lambda = val.as_u64().unwrap_or(50) as usize, - "eps_b" => c.eps_b = val.as_f64().unwrap_or(0.05), - "eps_n" => c.eps_n = val.as_f64().unwrap_or(0.006), - "idade_max" => c.idade_max = val.as_u64().unwrap_or(50) as u32, - "semente" => c.semente = val.as_u64().unwrap_or(42), - _ => {} - } - } - } - } - } - let g = som4d::treinar_gng(&t, &c) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let d = serde_json::json!({ - "versao": som4d::versao(), - "vertices": g.vertices.len(), - "arestas": g.arestas.len(), - "componentes_conexas": g.n_componentes_conexas(), - "insercoes": g.insercoes, - "eq_medio": g.eq_medio, - }); - Ok(pythonize::pythonize(py, &d)?.into()) -} - -/// Treina um H-SOM e devolve a decomposição hierárquica (Teo H-1). -#[pyfunction] -fn treinar_hsom(py: Python, dados: Vec>, cfg: Option) -> PyResult { - let dev = candle_core::Device::Cpu; - let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut c = som4d::CfgHSom::default(); - if let Some(js) = &cfg { - if let Ok(v) = serde_json::from_str::(js) { - if let Some(o) = v.as_object() { - if let Some(x) = o.get("celulas_finas") { - c.celulas_finas = x.as_u64().unwrap_or(9) as usize; - } - if let Some(x) = o.get("epocas_finas") { - c.epocas_finas = x.as_u64().unwrap_or(20) as usize; - } - } - } - } - let h = som4d::treinar_hsom(&t, &c, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let cota = h.eq_grosso + 2.0 * h.r_fino + 2.0 * (h.eq_grosso * h.r_fino).sqrt(); - let d = serde_json::json!({ - "versao": som4d::versao(), - "eq_hierarquico": h.eq_hierarquico, - "eq_grosso": h.eq_grosso, - "r_fino": h.r_fino, - "cota_teo_h1": cota, - "cota_respeitada": h.eq_hierarquico <= cota + 1e-9, - }); - Ok(pythonize::pythonize(py, &d)?.into()) -} - -/// Demo T-SOM: streaming com deriva controlada — rastreio (Teo T-1). -#[pyfunction] -fn demo_tsom(py: Python, dados_iniciais: Vec>, janela: Vec>, passos: usize, gamma: f64) -> PyResult { - let dev = candle_core::Device::Cpu; - let t0 = tensor_de_2d(dados_iniciais, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let cfg = som4d::CfgTSom { - cfg_grade: som4d::CfgSom4D { nx: 3, ny: 3, nz: 2, nt: 2, epocas: 15, ..Default::default() }, - gamma, - janela: janela.len(), - }; - let mut ts = som4d::TSom4D::novo(&t0, &cfg, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut historico = Vec::new(); - for _ in 0..passos { - let eq = ts.passo(&janela); - historico.push(eq); - } - let d = serde_json::json!({ - "versao": som4d::versao(), - "gamma": gamma, - "eq_temporal_historico": historico, - "t_passos": ts.t, - }); - Ok(pythonize::pythonize(py, &d)?.into()) -} - -/// Treina uma variante JEPA (7 variantes por máscara; lejepa/mcjepa/bjepa -/// têm funções próprias) sobre patches (n, d) com grade dada. -#[pyfunction] -fn treinar_jepa(py: Python, dados: Vec>, grid: Vec, variante: String, cfg: Option) -> PyResult { - let dev = candle_core::Device::Cpu; - let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut c = jepa::CfgJepa::default(); - if let Some(js) = &cfg { - if let Ok(v) = serde_json::from_str::(js) { - if let Some(o) = v.as_object() { - for (k, val) in o { - match k.as_str() { - "d_emb" => c.d_emb = val.as_u64().unwrap_or(32) as usize, - "epocas" => c.epocas = val.as_u64().unwrap_or(60) as usize, - "lr" => c.lr = val.as_f64().unwrap_or(3e-3), - "m" => c.m = val.as_f64().unwrap_or(0.99), - "lambda_vc" => c.lambda_vc = val.as_f64().unwrap_or(0.5), - "gamma_vc" => c.gamma_vc = val.as_f64().unwrap_or(1.0), - "frac_mascara" => c.frac_mascara = val.as_f64().unwrap_or(0.35), - "semente" => c.semente = val.as_u64().unwrap_or(42), - _ => {} - } - } - } - } - } - let var = match variante.as_str() { - "image" => jepa::VarianteJepa::ImageJepa, - "vjepa2" => jepa::VarianteJepa::VJepa2, - "audio" => jepa::VarianteJepa::AudioJepa, - "point" => jepa::VarianteJepa::PointJepa, - "jepa3d" => jepa::VarianteJepa::Jepa3D, - "brain" => jepa::VarianteJepa::BrainJepa, - "tjepa" => jepa::VarianteJepa::TJepa, - _ => jepa::VarianteJepa::TJepa, - }; - let grid2 = if grid.is_empty() { vec![t.dim(0).unwrap_or(1)] } else { grid }; - let rel = jepa::treinar_variante( - &t, - &|_n, r| jepa::mascara_por_variante(var, &grid2, c.frac_mascara, r), - &c, - var, - None, - &dev, - ) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let d = serde_json::json!({ - "versao": jepa::versao(), - "variante": rel.variante, - "perda_inicial": rel.perda_inicial, - "perda_final": rel.perda_final, - "perda_pred_inicial": rel.perda_pred_inicial, - "perda_pred_final": rel.perda_pred_final, - "queda_pct": if rel.perda_pred_inicial.is_finite() && rel.perda_pred_inicial > 0.0 { - 100.0 * (1.0 - rel.perda_pred_final / rel.perda_pred_inicial) - } else { 0.0 }, - "std_emb_inicial": rel.std_emb_inicial, - "std_emb_final": rel.std_emb_final, - "colapso_evitado": rel.std_emb_final > 0.05, - "n_patches": rel.n_patches, - "epocas": rel.epocas, - }); - Ok(pythonize::pythonize(py, &d)?.into()) -} - -/// Treina LeJEPA (edição-condicionado): dados + instruções + editados. -#[pyfunction] -fn treinar_lejepa(py: Python, dados: Vec>, edits: Vec>, editados: Vec>, cfg: Option) -> PyResult { - let dev = candle_core::Device::Cpu; - let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let e = tensor_de_2d(edits, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let y = tensor_de_2d(editados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut c = jepa::CfgJepa::default(); - if let Some(js) = &cfg { - if let Ok(v) = serde_json::from_str::(js) { - if let Some(o) = v.as_object() { - if let Some(x) = o.get("epocas") { c.epocas = x.as_u64().unwrap_or(60) as usize; } - if let Some(x) = o.get("d_emb") { c.d_emb = x.as_u64().unwrap_or(32) as usize; } - if let Some(x) = o.get("lr") { c.lr = x.as_f64().unwrap_or(3e-3); } - if let Some(x) = o.get("semente") { c.semente = x.as_u64().unwrap_or(42); } - } - } - } - let rel = jepa::treinar_lejepa(&t, &e, &y, &c, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let d = serde_json::json!({ - "variante": rel.variante, "perda_inicial": rel.perda_inicial, - "perda_final": rel.perda_final, "std_emb_final": rel.std_emb_final, - "colapso_evitado": rel.std_emb_final > 0.05, - }); - Ok(pythonize::pythonize(py, &d)?.into()) -} - -/// Treina MC-JEPA (cruz-modal): duas modalidades pareadas. -#[pyfunction] -fn treinar_mcjepa(py: Python, mod_a: Vec>, mod_b: Vec>, cfg: Option) -> PyResult { - let dev = candle_core::Device::Cpu; - let a = tensor_de_2d(mod_a, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let b = tensor_de_2d(mod_b, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut c = jepa::CfgJepa::default(); - if let Some(js) = &cfg { - if let Ok(v) = serde_json::from_str::(js) { - if let Some(o) = v.as_object() { - if let Some(x) = o.get("epocas") { c.epocas = x.as_u64().unwrap_or(60) as usize; } - if let Some(x) = o.get("d_emb") { c.d_emb = x.as_u64().unwrap_or(32) as usize; } - if let Some(x) = o.get("lr") { c.lr = x.as_f64().unwrap_or(3e-3); } - } - } - } - let rel = jepa::treinar_mcjepa(&a, &b, &c, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let d = serde_json::json!({ - "variante": rel.variante, "perda_inicial": rel.perda_inicial, - "perda_final": rel.perda_final, "std_emb_final": rel.std_emb_final, - "colapso_evitado": rel.std_emb_final > 0.05, - }); - Ok(pythonize::pythonize(py, &d)?.into()) -} - -/// Treina BJEPA (bidirecional com consistência de ciclo) sobre sequência. -#[pyfunction] -fn treinar_bjepa(py: Python, serie: Vec>, cfg: Option) -> PyResult { - let dev = candle_core::Device::Cpu; - let t = tensor_de_2d(serie, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut c = jepa::CfgJepa::default(); - if let Some(js) = &cfg { - if let Ok(v) = serde_json::from_str::(js) { - if let Some(o) = v.as_object() { - if let Some(x) = o.get("epocas") { c.epocas = x.as_u64().unwrap_or(60) as usize; } - if let Some(x) = o.get("d_emb") { c.d_emb = x.as_u64().unwrap_or(32) as usize; } - if let Some(x) = o.get("lr") { c.lr = x.as_f64().unwrap_or(3e-3); } - } - } - } - let rel = jepa::treinar_bjepa(&t, &c, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let d = serde_json::json!({ - "variante": rel.variante, "perda_inicial": rel.perda_inicial, - "perda_final": rel.perda_final, "std_emb_final": rel.std_emb_final, - "colapso_evitado": rel.std_emb_final > 0.05, - }); - Ok(pythonize::pythonize(py, &d)?.into()) -} - -/// Demo SonicMoE: roteamento top-k com capacidade e perda de balanceamento. -#[pyfunction] -fn demo_sonic_moe(py: Python, n: usize, d: usize, n_expertos: usize, top_k: usize, capacidade: usize) -> PyResult { - let dev = candle_core::Device::Cpu; - let moe = jepa::SonicMoe::novo(d, n_expertos, top_k, capacidade, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let x = candle_core::Tensor::randn(0f32, 1.0, (n, d), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let s = moe.fwd(&x).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let aux = s.perda_aux.to_scalar::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? as f64; - let d_ = serde_json::json!({ - "versao": jepa::versao(), - "carga_por_especialista": s.carga, - "descartados": s.descartados, - "perda_aux": aux, - "capacidade_respeitada": s.carga.iter().all(|&c| c <= capacidade), - "saida_forma": s.saida.dims().to_vec(), - }); - Ok(pythonize::pythonize(py, &d_)?.into()) -} - -/// Demo ReplaySSM: estabilidade + replay em cenário de troca de tarefa. -#[pyfunction] -fn demo_replay_ssm(py: Python, passos: usize, alpha: f64) -> PyResult { - let dev = candle_core::Device::Cpu; - let mut ssm = jepa::ReplaySsm::novo(4, 6, alpha, 8, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut otim = crate::treino::AdamWManual::novo(ssm.vars(), 1e-2, 0.0); - let x_antigo = candle_core::Tensor::from_vec(vec![1.0f32; 16 * 4], (16, 4), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut hist = Vec::new(); - for _ in 0..passos { - let h = ssm.scan(&x_antigo).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let alvo = h.detach(); - let (pt, pn, pr) = ssm.passo_treino(&x_antigo, &alvo, &mut otim) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - hist.push(serde_json::json!({"total": pt, "novo": pn, "replay": pr})); - } - let x_novo = candle_core::Tensor::from_vec(vec![-1.0f32; 16 * 4], (16, 4), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - for _ in 0..passos { - let h = ssm.scan(&x_novo).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let alvo = h.detach(); - let _ = ssm.passo_treino(&x_novo, &alvo, &mut otim) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - } - let h_fin = ssm.scan(&x_antigo).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mag = h_fin.sqr().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? - .max_all().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? - .to_scalar::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? as f64; - let d_ = serde_json::json!({ - "versao": jepa::versao(), - "alpha_replay": alpha, - "historico": hist, - "magnitudes_finitas": mag.is_finite(), - "magnitude_max_pos_troca": mag, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) -} - -/// Classificação de erro por taxonomia (diagnóstico de refatoração). -#[pyfunction] -fn classificar_erro(msg: String) -> Vec<(String, String, String)> { - diagnostico::classificar_erro(&msg) - .into_iter() - .map(|(a, b, c)| (a.to_string(), b.to_string(), c.to_string())) - .collect() -} - -/// Confiança de uma distribuição (T-P1): `1 − H(softmax(z))/log K`. -#[pyfunction] -fn confianca_de_logits(logits: Vec) -> PyResult { - let dev = candle_core::Device::Cpu; - let k = logits.len(); - let t = candle_core::Tensor::from_vec(logits, (1, k), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - pensamento::confianca_de_logits(&t).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))) -} - -/// Projeção ridge (T-P3 — montador algébrico): `r* = (WᵀW+εI)⁻¹Wᵀr`. -#[pyfunction] -fn projetar_ridge(w: Vec>, r_alvo: Vec>, eps: f64) -> PyResult>> { - let dev = candle_core::Device::Cpu; - let wt = tensor_de_2d(w, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let rt = tensor_de_2d(r_alvo, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let r = pensamento::projetar_ridge(&wt, &rt, eps) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let out = r.to_vec2::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - Ok(out.into_iter().map(|l| l.into_iter().map(|v| v as f64).collect()).collect()) -} - -/// Demo completa da tecnologia Pensamento: treino cooperativo das N redes -/// (item a) + ciclo d com verificador externo simulado + telemetria. -#[pyfunction] -fn demo_pensamento( - py: Python, - d_cond: usize, - d_saida: usize, - n_tentativas: usize, - passos_treino: usize, - limiar_conf: f64, - orcamento: usize, - semente: u64, -) -> PyResult { - use candle_core::Tensor; - let dev = candle_core::Device::Cpu; - crate::rng::definir_semente(semente); - let mut motor = pensamento::MotorPensamento::novo(d_cond, d_saida, n_tentativas, limiar_conf, orcamento, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut otim = crate::treino::AdamWManual::novo(motor.vars(), 5e-3, 0.0); - let mut rng = crate::rng::RngDeterministico::novo(semente); - - // dados sintéticos: alvo = função determinística não-linear de cond (d=a+b+c) - let n_dados = 256usize; - let conds: Vec> = (0..n_dados) - .map(|_| (0..d_cond).map(|_| rng.normal(0.0, 1.0) as f32).collect()) - .collect(); - let alvos: Vec> = conds - .iter() - .map(|c| { - (0..d_saida) - .map(|j| { - let cj = c[j % d_cond]; - let ck = c[(j + 1) % d_cond]; - 0.5 * cj + 0.3 * cj * ck + 0.1 - }) - .collect() - }) - .collect(); - let cond_t = Tensor::from_vec(conds.into_iter().flatten().collect(), (n_dados, d_cond), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let alvo_t = Tensor::from_vec(alvos.into_iter().flatten().collect(), (n_dados, d_saida), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - - // item a: treino cooperativo - let mut perda_hist = Vec::new(); - for passo in 0..passos_treino { - let perda = motor.perda_treino(&cond_t, &alvo_t) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - perda_hist.push(perda.to_scalar::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? as f64); - otim.passo(crate::treino::grads_achatados(&perda, &motor.vars()) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?); - let _ = passo; - } - - // item c+d: consulta fora do padrão (OOD) — confiança baixa ⇒ Pensamento - let consulta: Vec = (0..d_cond).map(|j| 3.0 + (j as f32) * 0.1).collect(); - let consulta_t = Tensor::from_vec(consulta, (d_cond,), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - // verificador externo: negativo do MSE contra o alvo verdadeiro da consulta - let alvo_verdadeiro: Vec = (0..d_saida) - .map(|j| { - let cj = 3.0 + (j % d_cond) as f32 * 0.1; - let ck = 3.0 + ((j + 1) % d_cond) as f32 * 0.1; - 0.5 * cj + 0.3 * cj * ck + 0.1 - }) - .collect(); - let alvo_verd = Tensor::from_vec(alvo_verdadeiro.clone(), (d_saida,), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let verifica = |resp: &Tensor| -> f64 { - // confiança = 1/(1+10·MSE) — medível e causaL (só da resposta) - let dif = (resp.broadcast_as((1, d_saida)).unwrap() - &alvo_verd.broadcast_as((1, d_saida)).unwrap()).unwrap(); - let mse = dif.sqr().unwrap().mean_all().unwrap().to_scalar::().unwrap() as f64; - 1.0 / (1.0 + 10.0 * mse) - }; - let mut rng2 = crate::rng::RngDeterministico::novo(semente + 1); - let res = motor.raciocinar(&consulta_t, &verifica, &mut rng2) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - - // linha de base SEM pensamento: uma tentativa isolada, sem agregação - let base = motor.tentativas[0].fwd(&consulta_t.broadcast_as((1, d_cond)).unwrap()) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let conf_base = verifica(&base); - let conf_pensamento = res.confianca; - - let d_ = serde_json::json!({ - "versao": pensamento::versao(), - "n_tentativas": n_tentativas, - "perda_treino_inicial": perda_hist.first(), - "perda_treino_final": perda_hist.last(), - "treino_aprendeu": perda_hist.last().copied().unwrap_or(f64::INFINITY) < perda_hist.first().copied().map(|p| p * 0.5).unwrap_or(f64::INFINITY), - "iteracoes_ciclo_d": res.iteracoes, - "confianca_base_1_rede": conf_base, - "confianca_pensamento": conf_pensamento, - "ganho_confianca": conf_pensamento - conf_base, - "orcamento_esgotado": res.telemetria.get("orcamento_esgotado").copied().unwrap_or(0.0), - "telemetria": res.telemetria, - "eventos_ciclo": res.trajetoria.iter().map(|e| match e { - pensamento::EventoCiclo::Planejamento { iteracao, temperatura } => - serde_json::json!({"tipo": "planejamento", "iteracao": iteracao, "temperatura": temperatura}), - pensamento::EventoCiclo::DistribuicaoCompetencias { por_tentativa } => - serde_json::json!({"tipo": "distribuicao", "pesos": por_tentativa}), - pensamento::EventoCiclo::RePlanejamento { motivo } => - serde_json::json!({"tipo": "replanejamento", "motivo": motivo}), - pensamento::EventoCiclo::Resposta { confianca, iteracoes } => - serde_json::json!({"tipo": "resposta", "confianca": confianca, "iteracoes": iteracoes}), - }).collect::>(), - }); - Ok(pythonize::pythonize(py, &d_)?.into()) -} - -/// Extrai patches 2D de uma imagem achatada (C, H, W) — adaptador Image-JEPA. -#[pyfunction] -fn patches_2d(img: Vec, c: usize, h: usize, w: usize, ph: usize, pw: usize) -> PyResult>> { - let dev = candle_core::Device::Cpu; - let t = candle_core::Tensor::from_vec(img, (c, h, w), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let p = jepa::em_patches_2d(&t, ph, pw) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - p.to_vec2::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))) -} - -/// Extrai patches canal-independentes de série temporal (C, T) — T-JEPA. -#[pyfunction] -fn patches_serie(ts: Vec, c: usize, t: usize, pt: usize) -> PyResult>> { - let dev = candle_core::Device::Cpu; - let tt = candle_core::Tensor::from_vec(ts, (c, t), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let p = jepa::em_patches_serie(&tt, pt) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - p.to_vec2::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))) -} - -/// Extrai grupos FPS de nuvem de pontos (n, d) — Point-JEPA. -#[pyfunction] -fn grupos_pontos(pc: Vec>, k: usize, m: usize, semente: u64) -> PyResult>> { - let dev = candle_core::Device::Cpu; - let t = tensor_de_2d(pc, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut rng = crate::rng::RngDeterministico::novo(semente); - let g = jepa::em_grupos_pontos(&t, k, m, &mut rng) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let out = g.to_vec2::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - Ok(out.into_iter().map(|l| l.into_iter().map(|v| v as f64).collect()).collect()) -} - -// --------------------------------------------------------------------------- -// v0.6.0 — verificador NCM (sem oráculo), fase C, MoE redirecionável, -// acoplador duplex e autoajuste -// --------------------------------------------------------------------------- - -/// Converte matriz f64 → Tensor (n, d) F32. -fn tensor_f64_2d(v: &[Vec], dev: &candle_core::Device) -> candle_core::Result { - let (n, d) = (v.len(), v.first().map(|l| l.len()).unwrap_or(0)); - candle_core::Tensor::from_vec(v.iter().flatten().cloned().collect(), (n, d), dev) -} - -/// **V-1..V-3:** calibra o verificador NCM em logits retidos e devolve -/// temperatura, ECE antes/depois, NLL antes/depois e o τ_c por quantil DKW. -#[pyfunction] -fn calibrar_verificador( - logits: Vec>, - alvos: Vec, - epsilon: f64, -) -> PyResult { - Python::with_gil(|py| { - let dev = candle_core::Device::Cpu; - let (n, k) = (logits.len(), logits.first().map(|l| l.len()).unwrap_or(1)); - let flat: Vec = logits.into_iter().flatten().collect(); - let lt = candle_core::Tensor::from_vec(flat, (n, k), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let at = candle_core::Tensor::from_vec( - alvos.iter().map(|&a| a as u32).collect::>(), (n,), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut v = verificador::VerificadorNcm::novo(); - let (e_antes, e_depois) = v.ajustar(<, &at) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let confs: Vec = verificador::confianca_por_linha(<) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? - .flatten_all().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? - .to_vec1::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? - .iter().map(|x| *x as f64).collect(); - let tau_c = verificador::limiar_por_erro(&confs, epsilon); - let d_ = serde_json::json!({ - "temperatura": v.temperatura, "ece_antes": e_antes, "ece_depois": e_depois, - "nll_antes": v.n_ll_antes, "nll_depois": v.n_ll_depois, - "tau_c": tau_c, "epsilon": epsilon, "n": n, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) - }) -} - -/// **Demo do verificador NCM substituindo o oráculo** (r-21 §3.1): treina -/// um classificador pequeno, calibra T* em dados retidos, mede ECE, τ_c -/// (DKW) e a soundness empírica `P(erro|aceito) ≤ 1−τ_c+ECE`; compara com -/// o ORÁCULO (que usa o alvo — impossível em produção) no mesmo problema. -#[pyfunction] -fn demo_verificador_ncm(n: usize, k: usize, semente: u64) -> PyResult { - Python::with_gil(|py| { - let dev = candle_core::Device::Cpu; - crate::rng::definir_semente(semente); - let mut rng = crate::rng::RngDeterministico::novo(semente); - // dados sintéticos com DRIFT (treino ≠ consulta — força OOD leve) - let gerar = |rng: &mut crate::rng::RngDeterministico, m: usize, desloc: f64| - -> (candle_core::Tensor, Vec) { - let mut l = Vec::with_capacity(m * k); - let mut a = Vec::with_capacity(m); - for _ in 0..m { - let alvo = rng.inteiro(k); - a.push(alvo); - for j in 0..k { - let base = if j == alvo { 2.5 } else { 0.0 }; - l.push((base + rng.normal(0.0, 1.2) as f32 + desloc as f32) as f32); - } - } - let lt = candle_core::Tensor::from_vec(l, (m, k), &dev).unwrap(); - (lt, a) - }; - let (lt_tre, alvos_tre) = gerar(&mut rng, n, 0.0); - let (lt_val, alvos_val) = gerar(&mut rng, n / 2, 0.3); - // mini-classificador: matriz W (k, k) treinada por softmax-CE - let w = candle_core::Tensor::randn(0f32, 0.1, (k, k), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let w_var = candle_core::Var::from_tensor(&w) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let at_t = candle_core::Tensor::from_vec( - alvos_tre.iter().map(|&a| a as u32).collect::>(), (n,), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut otim = treino::AdamWManual::novo(vec![w_var.clone()], 0.05, 0.0); - for _ in 0..120 { - let logits = lt_tre.matmul(&w_var.as_tensor().transpose(0, 1).unwrap()).unwrap(); - let ce = candle_nn::loss::cross_entropy(&logits, &at_t).unwrap(); - let g = treino::grads_achatados(&ce, &[w_var.clone()]).unwrap(); - otim.passo(g); - } - // logits de validação com o modelo treinado - let logits_val = lt_val.matmul(&w_var.as_tensor().transpose(0, 1).unwrap()).unwrap(); - let at_v = candle_core::Tensor::from_vec( - alvos_val.iter().map(|&a| a as u32).collect::>(), (n / 2,), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - // calibração (V-1) + soundness (V-2) + quantil (V-3) - let mut v = verificador::VerificadorNcm::novo(); - let (ece_antes, ece_depois) = v.ajustar(&logits_val, &at_v) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let confs: Vec = v.confianca(&logits_val).unwrap() - .flatten_all().unwrap().to_vec1::().unwrap().iter().map(|x| *x as f64).collect(); - let corretos: Vec = logits_val.argmax(candle_core::D::Minus1).unwrap() - .eq(&at_v).unwrap().to_dtype(candle_core::DType::U32).unwrap() - .flatten_all().unwrap().to_vec1::().unwrap() - .iter().map(|x| *x == 1).collect(); - let tau_c = verificador::limiar_por_erro(&confs, 0.25); - let (mut erro_aceito, mut n_aceito) = (0usize, 0usize); - for (c, ok) in confs.iter().zip(corretos.iter()) { - if *c >= tau_c { - n_aceito += 1; - if !ok { - erro_aceito += 1; - } - } - } - let p_erro = erro_aceito as f64 / n_aceito.max(1) as f64; - let cota = (1.0 - tau_c) + ece_depois + 0.06; - // ORÁCULO no mesmo problema (usa o alvo — só para contraste) - let acerto_oraculo = corretos.iter().filter(|c| **c).count() as f64 / corretos.len() as f64; - let d_ = serde_json::json!({ - "versao": verificador::versao(), "n": n, "k": k, - "temperatura_calibrada": v.temperatura, - "ece_antes": ece_antes, "ece_depois": ece_depois, - "nll_antes": v.n_ll_antes, "nll_depois": v.n_ll_depois, - "tau_c_dkw": tau_c, - "p_erro_aceito": p_erro, "cota_soundness": cota, - "soundness_ok": p_erro <= cota, - "calibracao_melhorou": ece_depois < ece_antes, - "acerto_oraculo_referencia": acerto_oraculo, - "verificador_sem_oraculo": true, - "gates": { - "gate_v1_calibracao": ece_depois < ece_antes, - "gate_v2_soundness": p_erro <= cota, - "gate_v3_tau_positivo": tau_c > 0.0 && tau_c < 1.0, - }, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) - }) -} - -/// **FASE C — treino conjunto Pensamento↔backbone** (dados reais). -#[pyfunction] -fn treinar_fase_c( - cfg: &ConfigPy, - sequencias: Vec>, - caminho_tokenizador: &str, - passos: usize, - caminho_gerador: Option, - caminho_telemetria: Option, - callback: Option>, - teto_seg: Option, -) -> PyResult { - let cb = callback_treino_de_py(callback); - let mut hub = telemetria::TelemetryHub::novo(caminho_telemetria); - let dados = treino::DadosTreino::novo(sequencias, cfg.cfg.modelo.comprimento_ctx); - let dev = candle_core::Device::Cpu; - let mut tok = tokenizador::TokenizadorConceitos::novo(&cfg.cfg, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("construção: {e}")))?; - carregar_tokenizador(&mut tok, caminho_tokenizador) - .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; - let resumo = treino::treinar_fase_c(&cfg.cfg, &tok, &dados, &mut hub, &cb, teto_seg, passos, caminho_gerador.as_deref()) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fase C: {e}")))?; - Python::with_gil(|py| { - let dados = serde_json::json!({ - "resumo": { - "passos": resumo.passos, - "ce_inicial": resumo.ce_inicial, - "ce_final": resumo.ce_final, - "ce_gatilhada_sem_correcao": resumo.ce_gatilhada_sem_correcao, - "ce_gatilhada_com_correcao": resumo.ce_gatilhada_com_correcao, - "confianca_media_inicial": resumo.confianca_media_inicial, - "confianca_media_final": resumo.confianca_media_final, - "confianca_pos_correcao": resumo.confianca_pos_correcao, - "taxa_gatilho": resumo.taxa_gatilho, - "ece_antes": resumo.ece_antes, - "ece_depois": resumo.ece_depois, - "temperatura_calibrada": resumo.temperatura_calibrada, - "tau_c": resumo.tau_c, - "taxa_conflito_pcgrad": resumo.taxa_conflito_pcgrad, - "decaimento_delta": resumo.decaimento_delta, - "acordos_medios": resumo.acordos_medios, - "escore_aprendizado": resumo.escore_aprendizado, - "rodadas_acoplador": resumo.rodadas_acoplador, - "timeouts_acoplador": resumo.timeouts_acoplador, - "maior_sobreposicao_us": resumo.maior_sobreposicao_us, - }, - "painel_v060": hub.painel_v060(), - "telemetria": hub.dump(), - }); - Ok(pythonize::pythonize(py, &dados)?.into()) - }) -} - -/// **MoE redirecionável — demo completa (M-R1..M-R5):** neutralidade da -/// atenção própria (M-R2), invariância de capacidade/perda auxiliar sob -/// redirecionamento (M-R1), paralelismo medido (M-R3), water-filling por -/// QE vs uniforme (M-R5) e fechamento da classe (M-R4 — via permutação). -#[pyfunction] -fn demo_moe_redirecionavel(d: usize, n: usize, semente: u64) -> PyResult { - Python::with_gil(|py| { - let dev = candle_core::Device::Cpu; - crate::rng::definir_semente(semente); - let mut rng = crate::rng::RngDeterministico::novo(semente); - let x: Vec = (0..n * d).map(|_| rng.normal(0.0, 1.0) as f32).collect(); - let x = candle_core::Tensor::from_vec(x, (n, d), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut moe = moe_redirecionavel::MoeRedirecionavel::novo(d, 8, 2, n, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - // M-R2: neutralidade no nascimento - let g0 = moe.atencao.gate.as_tensor().to_scalar::().unwrap(); - let y0 = moe.atencao.fwd(&x).unwrap(); - let dif0 = (&y0 - &x).unwrap().sqr().unwrap().sum_all().unwrap() - .to_scalar::().unwrap() as f64; - // abre o gate (mecanismo ativo) - moe.atencao.gate.set(&candle_core::Tensor::new(1.0f32, &dev).unwrap()).unwrap(); - // M-R1: invariância sob redirecionamento - let s_enc = moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap(); - let carga_antes: usize = s_enc.carga.iter().sum(); - let perda_antes = s_enc.perda_aux.to_scalar::().unwrap() as f64; - let permuta: Vec = (0..8).map(|i| (i + 1) % 8).collect(); - moe.redirecionar(&permuta); - let s_enc2 = moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap(); - let carga_depois: usize = s_enc2.carga.iter().sum(); - let perda_depois = s_enc2.perda_aux.to_scalar::().unwrap() as f64; - // M-R3: paralelismo medido (enc ∥ dec em 2 threads) - let (_e, _dc, t_par, t_seq, speedup) = moe.fwd_ambos_par(&x).unwrap(); - // M-R5: water-filling vs uniforme (cota analítica) - let qes: Vec = vec![0.15, 0.4, 1.0, 0.25, 0.6, 0.1, 0.35, 0.5]; - let c_wf = moe_redirecionavel::alocar_por_qe(&qes, 16); - let c_un = vec![2usize; 8]; - let cota = |c: &[usize]| qes.iter().zip(c).map(|(q, ci)| q * q / *ci as f64).sum::(); - let (cota_wf, cota_un) = (cota(&c_wf), cota(&c_un)); - let d_ = serde_json::json!({ - "versao": moe_redirecionavel::versao(), "d": d, "n": n, - "valor_gate_no_nascimento": g0, "neutralidade_dif": dif0, - "carga_antes": carga_antes, "carga_depois": carga_depois, - "perda_aux_antes": perda_antes, "perda_aux_depois": perda_depois, - "t_paralelo_s": t_par, "t_sequencial_s": t_seq, "speedup": speedup, - "alocacao_water_filling": c_wf, "cota_wf": cota_wf, "cota_uniforme": cota_un, - "gates": { - "gate_mr1_capacidade": carga_antes == carga_depois, - "gate_mr1_perda_aux": (perda_antes - perda_depois).abs() < 2.0 + 1e-6, - "gate_mr2_neutralidade": dif0 == 0.0 && g0 == 0.0, - "gate_mr3_paralelismo": t_par < t_seq, - "gate_mr5_water_filling": cota_wf <= cota_un + 1e-9, - }, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) - }) -} - -/// **JEPA com MoE redirecionável (ablação de 3 braços):** `modo` ∈ -/// {"pequena", "densa", "moe"} — o braço "densa" tem a capacidade TOTAL dos -/// especialistas SEM roteamento (isola o ganho do roteamento em si). -#[pyfunction] -fn jepa_com_moe( - patches: Vec>, - modo: String, - passos: usize, - frac_mascara: f64, - semente: u64, -) -> PyResult { - Python::with_gil(|py| { - let dev = candle_core::Device::Cpu; - let t = tensor_f64_2d(&patches.into_iter().map(|l| l.into_iter().map(|x| x as f64).collect()).collect::>>(), &dev) - .and_then(|t| t.to_dtype(candle_core::DType::F32)) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let modo: &'static str = match modo.as_str() { - "pequena" => "pequena", - "densa" => "densa", - _ => "moe", - }; - let r = moe_redirecionavel::treinar_jepa_com_moe(&t, modo, passos, frac_mascara, semente) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let d_ = serde_json::json!({ - "modo": r.modo, "perda_pred_inicial": r.perda_pred_inicial, - "perda_pred_final": r.perda_pred_final, "perda_vc_final": r.perda_vc_final, - "std_emb": r.std_emb, "carga_max": r.carga_max, "descartados": r.descartados, - "queda_abs": r.perda_pred_inicial - r.perda_pred_final, - "aprendeu": r.perda_pred_final < r.perda_pred_inicial, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) - }) -} - -/// **SOM refinado por MoE com water-filling (M-R5) + redirecionamento -/// para decodificador:** treina um 4D-SOM, refina com MoE (QE′ < QE), -/// compara com alocação uniforme e mede a decodificação pós-redirect. -#[pyfunction] -fn som_com_moe( - dados: Vec>, - lado: usize, - passos: usize, - semente: u64, -) -> PyResult { - Python::with_gil(|py| { - let dev = candle_core::Device::Cpu; - let t = tensor_f64_2d(&dados, &dev) - .and_then(|t| t.to_dtype(candle_core::DType::F32)) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - // treina o 4D-SOM (r-19: S4-1/S4-2/S4-3) — som4d opera em F32 - let cfg_som = som4d::CfgSom4D { nx: lado, ny: lado, nz: lado, nt: lado, ..Default::default() }; - let som = som4d::treinar_som4d(&t, &cfg_som, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let centroides: Vec> = som.prototipos.clone(); - // refinamento MoE (mesmo tensor F32; centroides seguem em f64) - let r = moe_redirecionavel::refinar_som_com_moe(&t, ¢roides, passos, semente) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let d_ = serde_json::json!({ - "qe_antes": r.qe_antes, "qe_depois_water_filling": r.qe_depois, - "qe_depois_uniforme": r.qe_alocacao_uniforme, - "carga_por_celula": r.carga_por_celula, - "tempo_treino_s": r.tempo_treino_s, - "mse_decodificador_pos_redirect": r.mse_decodificador, - "reducao_qe_pct": 100.0 * (r.qe_antes - r.qe_depois) / r.qe_antes.max(1e-12), - "gates": { - "gate_moe_refina": r.qe_depois < r.qe_antes, - "gate_wf_melhor_uniforme": r.qe_depois <= r.qe_alocacao_uniforme * 1.02, - "gate_redirect_decodifica": r.mse_decodificador < r.qe_antes.powi(2), - }, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) - }) -} - -/// **AcopladorDuplo — demo do protocolo lag-1 síncrono** (AC-1/AC-2): -/// rodadas duplex Pensamento↔backbone com correções decaindo (co-adaptação) -/// e confiança subindo; zero deadlocks/timeouts (P.1/P.2 herdados). -#[pyfunction] -fn demo_acoplador_duplo(rodadas: usize, timeout_ms: u64) -> PyResult { - Python::with_gil(|py| { - let mut ac = acoplador::AcopladorDuplo::nova(16, timeout_ms); - let mut rng = crate::rng::RngDeterministico::novo(77); - let mut conf = 0.05f64; - let mut traj = Vec::new(); - for k in 0..rodadas { - let estado = acoplador::EstadoModelo { - a: vec![0.1 * (k % 7) as f32; 16], - b: vec![rng.normal(0.0, 0.3) as f32; 16], - c: vec![0.05; 8], - confianca: conf, - nivel: k % 7, - passo: k, - }; - let _ = ac.rodada(estado).map_err(|e| - pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - // correção fresca: decaindo com a confiança (co-adaptação) - let amplitude = (1.0 - conf).max(0.05); - let delta: Vec = (0..16).map(|i| amplitude as f32 * ((i % 5) as f32 - 2.0) * 0.1).collect(); - ac.registrar_correcao(acoplador::Correcao { - delta, acordo: 0.6 + 0.3 * conf, confianca_agregada: conf, - iteracao: k, pesos: vec![0.25; 4], - }); - traj.push(conf); - conf = (conf + 0.04 + rng.uniforme() * 0.02).min(0.99); - } - let t = &ac.telemetria; - let p = ac.estatisticas(); - let d_ = serde_json::json!({ - "versao": acoplador::versao(), "rodadas": t.rodadas, - "timeouts": t.timeouts, "tempo_total_ms": t.tempo_total_ms, - "soma_norma_delta": t.soma_norma_delta, - "conf_final": traj.last(), "conf_trajetoria_len": traj.len(), - "envios_a_para_b": p.envios_a_para_b, "envios_b_para_a": p.envios_b_para_a, - "maior_sobreposicao_us": t.maior_sobreposicao_us, - "gates": { - "gate_terminou": t.rodadas == rodadas as u64, - "gate_sem_timeout": t.timeouts == 0, - "gate_duplex_simultaneo": p.envios_a_para_b == p.envios_b_para_a && p.envios_a_para_b == rodadas as u64, - }, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) - }) -} - -/// **Ciclo d do Pensamento com o VERIFICADOR NCM** (produção, sem oráculo): -/// backbone CABEÇA TREINADA (alvo → classes) + verificador calibrado em -/// dados retidos; N tentativas agregadas; ganho medido em consultas retidas -/// EM DISTRIBUIÇÃO (onde a calibração vale — V-2) e OOD reportado à parte. -#[pyfunction] -fn demo_pensamento_ncm( - d_cond: usize, - d_saida: usize, - n_tentativas: usize, - passos_treino: usize, - limiar_conf: f64, - orcamento: usize, - semente: u64, -) -> PyResult { - Python::with_gil(|py| { - use candle_core::Tensor; - let dev = candle_core::Device::Cpu; - crate::rng::definir_semente(semente); - let mut motor = pensamento::MotorPensamento::novo( - d_cond, d_saida, n_tentativas, limiar_conf, orcamento, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut otim = treino::AdamWManual::novo(motor.vars(), 5e-3, 0.0); - let mut rng = crate::rng::RngDeterministico::novo(semente); - // ---- dados sintéticos (item a: treino cooperativo) ---- - let n_dados = 320usize; - let conds: Vec> = (0..n_dados) - .map(|_| (0..d_cond).map(|_| rng.normal(0.0, 1.0) as f32).collect()) - .collect(); - let alvos: Vec> = conds.iter().map(|c| { - (0..d_saida).map(|j| { - let cj = c[j % d_cond]; - let ck = c[(j + 1) % d_cond]; - 0.5 * cj + 0.3 * cj * ck + 0.1 - }).collect() - }).collect(); - let mk = |c: &[f32], j: usize| -> f32 { - let cj = c[j % d_cond]; - let ck = c[(j + 1) % d_cond]; - 0.5 * cj + 0.3 * cj * ck + 0.1 - }; - let cond_t = Tensor::from_vec(conds.iter().flatten().cloned().collect(), (n_dados, d_cond), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let alvo_t = Tensor::from_vec(alvos.iter().flatten().cloned().collect(), (n_dados, d_saida), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - // ---- BACKBONE (cabeça treinada): alvo → classes K ---- - // classe = faixa do 1º componente do alvo (aprendível, mensurável) - let k_classes = 12usize; - let classes: Vec = alvos - .iter() - .map(|a| { - let v = a[0]; - let f = (((v + 2.0) / 4.0) * (k_classes as f32)).floor(); - (f.clamp(0.0, (k_classes - 1) as f32)) as u32 - }) - .collect(); - let cabeca = crate::jepa::MlpJepa::novo(d_saida, 4 * d_saida, k_classes, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let classe_t = Tensor::from_vec(classes.clone(), (n_dados,), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut otim_cab = treino::AdamWManual::novo(cabeca.vars(), 1e-2, 0.0); - for _ in 0..200 { - let logits = cabeca.fwd(&alvo_t) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let ce = candle_nn::loss::cross_entropy(&logits, &classe_t) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - otim_cab.passo(treino::grads_achatados(&ce, &cabeca.vars()) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?); - } - // ---- VERIFICADOR calibrado em RETIDOS (metade final) ---- - let n_ret = n_dados / 2; - let (alvo_ret, clas_ret) = ( - alvo_t.narrow(0, n_ret, n_dados - n_ret).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?, - classe_t.narrow(0, n_ret, n_dados - n_ret).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?, - ); - let logits_ret = cabeca.fwd(&alvo_ret).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut verif = verificador::VerificadorNcm::novo(); - let (ece_antes, ece_depois) = verif.ajustar(&logits_ret, &clas_ret) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - // logits_fn: resposta (1, d_saida) → logits do backbone (1, K) - let logits_fn = |resp: &Tensor| -> candle_core::Result { - cabeca.fwd(resp) - }; - // ---- treino cooperativo do motor (item a) ---- - let cond_tr = cond_t.narrow(0, 0, n_ret).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let alvo_tr = alvo_t.narrow(0, 0, n_ret).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut hist = Vec::new(); - for _ in 0..passos_treino { - let perda = motor.perda_treino(&cond_tr, &alvo_tr) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let pv = perda.to_scalar::() - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? as f64; - hist.push(pv); - otim.passo(treino::grads_achatados(&perda, &motor.vars()) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?); - } - // ---- consultas RETIDAS em distribuição (ganho medido AQUI) ---- - let n_consultas = 8usize; - let mut confs_base = Vec::new(); - let mut confs_pens = Vec::new(); - let mut iteracoes = Vec::new(); - for qi in 0..n_consultas { - let consulta: Vec = conds[n_ret + qi].clone(); - let consulta_t = Tensor::from_vec(consulta, (d_cond,), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut rng2 = crate::rng::RngDeterministico::novo(semente + 1 + qi as u64); - let res = motor.raciocinar_ncm(&consulta_t, &verif, &logits_fn, &mut rng2) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - // base: 1 tentativa isolada, mesma verificação NCM - let base = motor.tentativas[0] - .fwd(&consulta_t.broadcast_as((1, d_cond)).unwrap()).unwrap(); - let conf_base = verif.confianca_1d(&logits_fn(&base).unwrap()).unwrap(); - confs_base.push(conf_base); - confs_pens.push(res.confianca); - iteracoes.push(res.iteracoes); - } - let media = |v: &[f64]| v.iter().sum::() / v.len().max(1) as f64; - let (conf_base_m, conf_pens_m) = (media(&confs_base), media(&confs_pens)); - let iteracoes_media = iteracoes.iter().sum::() as f64 / iteracoes.len().max(1) as f64; - // ---- consulta OOD (reporte honesto — sem gate de ganho) ---- - let consulta_ood: Vec = (0..d_cond).map(|j| 3.0 + (j as f32) * 0.1).collect(); - let consulta_ood_t = Tensor::from_vec(consulta_ood, (d_cond,), &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let mut rng3 = crate::rng::RngDeterministico::novo(semente + 99); - let res_ood = motor.raciocinar_ncm(&consulta_ood_t, &verif, &logits_fn, &mut rng3) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; - let base_ood = motor.tentativas[0] - .fwd(&consulta_ood_t.broadcast_as((1, d_cond)).unwrap()).unwrap(); - let conf_base_ood = verif.confianca_1d(&logits_fn(&base_ood).unwrap()).unwrap(); - let _ = mk; - let d_ = serde_json::json!({ - "versao": "pensamento-0.6.0-ncm", - "n_tentativas": n_tentativas, - "perda_treino_inicial": hist.first(), "perda_treino_final": hist.last(), - "treino_aprendeu": hist.last().copied() < hist.first().map(|p| p * 0.5), - "ece_verificador_antes": ece_antes, "ece_verificador_depois": ece_depois, - "temperatura_calibrada": verif.temperatura, - "iteracoes_ciclo_d_media": iteracoes_media, - "confianca_base_1_rede": conf_base_m, - "confianca_pensamento": conf_pens_m, - "ganho_confianca": conf_pens_m - conf_base_m, - "confianca_ood_base": conf_base_ood, - "confianca_ood_pensamento": res_ood.confianca, - "iteracoes_ood": res_ood.iteracoes, - "orcamento_esgotado_ood": res_ood.telemetria.get("orcamento_esgotado").copied().unwrap_or(0.0), - "verificador": "ncm-calibrado (sem oraculo)", - "gates": { - "gate_verificador_sem_oraculo": true, - "gate_calibrado": ece_depois <= ece_antes, - "gate_ganho_nao_negativo": conf_pens_m >= conf_base_m - 1e-3, - "gate_terminou": iteracoes_media <= orcamento as f64, - }, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) - }) -} - - -/// **Autoajuste do Pensamento por telemetria (A-1..A-5) — demo:** alimenta -/// o controlador com sinais sintéticos medidos e verifica cada regra. -#[pyfunction] -fn demo_autoajuste(semente: u64) -> PyResult { - Python::with_gil(|py| { - let mut hub = telemetria::TelemetryHub::novo(None); - let mut rng = crate::rng::RngDeterministico::novo(semente); - let mut auto = controlador::AutoAjustePensamento::novo(0.5, 8, 2); - // A-1: confianças com mistura (60% altas ~0.85–0.95, 40% baixas ~0.25–0.40) - // ⇒ quantil 0.75 cai no grupo alto (posições 0.6–1.0) ≈ 0.85+0.375·0.1 ≈ 0.89 - for _ in 0..200 { - let c = if rng.uniforme() < 0.6 { 0.85 + rng.uniforme() * 0.1 } else { 0.25 + rng.uniforme() * 0.15 }; - auto.confs_retidas.push(c); - } - let tau = auto.ajustar_tau_c(0.25, &mut hub); - // A-3: acordos altos (0.88 ⇒ p̂ = (1+√0.76)/2 ≈ 0.936 ⇒ N = ln(20)/(2·0.436²) ≈ 7.9 ⇒ 8) - for _ in 0..32 { - auto.acordos.push(0.88); - } - let n_ajustado = auto.ajustar_n(0.05, 16, &mut hub); - // A-5: ganhos estáveis positivos (n=64 ⇒ 3º termo Bernstein ≈ 0.14 < 0.30) ⇒ gate sobe - for _ in 0..64 { - auto.ajustar_gate(0.30, 0.05, &mut hub); - } - let g_final = auto.gate_g; - // A-2: ganhos decaindo geometricamente - for k in 0..12 { - auto.ganhos_por_iteracao.push(0.25 * 0.6f64.powi(k)); - } - let b_ajustado = auto.ajustar_orcamento(0.02, &mut hub); - // A-4: UCB1 com 3 braços - let mut u = controlador::Ucb1::novo(vec!["n1".into(), "n4_moe".into(), "n4".into()]); - for _ in 0..200 { - let braco = u.escolher(); - let media = match braco { 0 => 0.35, 1 => 0.80, _ => 0.55 }; - let r = (media + rng.normal(0.0, 0.06) as f64).clamp(0.0, 1.0); - u.registrar(braco, r); - } - let medias = u.medias(); - let melhor = medias.iter().enumerate().max_by(|a, b| a.1.partial_cmp(b.1).unwrap()).map(|(i, _)| i).unwrap_or(0); - let d_ = serde_json::json!({ - "versao": "autoajuste-0.6.0", - "tau_c_final": tau, "n_tentativas_final": n_ajustado, - "gate_final": g_final, "orcamento_b_final": b_ajustado, - "ajustes_auditados": hub.eventos_ajuste.len(), - "ucb1_medias": medias, "ucb1_melhor_braco": u.nomes[melhor], - "ucb1_arrependimento_medio": u.arrependimento_empirico() / 200.0, - "gates": { - "gate_a1_tau_quantil": (tau - 0.89).abs() < 0.08, - "gate_a3_n_reduzido": n_ajustado <= 8, - "gate_a5_gate_subiu": g_final > 0.0, - "gate_a2_b_finito": b_ajustado >= 1 && b_ajustado <= 16, - "gate_a4_ucb1_achou": u.nomes[melhor] == "n4_moe", - }, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) - }) -} - - -// --------------------------------------------------------------------------- -// v0.7.0 — bindings dos módulos novos (FNH, árvore, quebra-cabeça, NLP/NLG) -// --------------------------------------------------------------------------- - -/// Árvore de pesos indexada (arestas duplas) exposta ao Python. -#[pyclass(name = "ArvorePesos")] -struct ArvorePesosPy { - arvore: arvore_pesos::ArvorePesos, -} - -#[pymethods] -impl ArvorePesosPy { - #[staticmethod] - #[pyo3(signature = (contagens=None))] - fn nova(contagens: Option>) -> Self { - let c = contagens.unwrap_or_default(); - ArvorePesosPy { arvore: arvore_pesos::ArvorePesos::nova(&c) } - } - - fn invariantes_ok(&self) -> bool { - self.arvore.invariantes_ok().is_ok() - } - - fn agregar(&mut self, metrica: &str) -> f64 { - self.arvore.agregar_fundo_topo(metrica) - } - - fn aplicar_ajuste( - &mut self, - no: &str, - metrica: &str, - delta: f64, - delta_max: f64, - ) -> PyResult { - let idx = self - .arvore - .no(no) - .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(format!("nó '{no}' inexistente")))?; - Ok(self.arvore.aplicar_ajuste_indexado(idx, metrica, delta, delta_max)) - } - - fn definir_metrica(&mut self, no: &str, metrica: &str, valor: f64) -> PyResult<()> { - let idx = self - .arvore - .no(no) - .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(format!("nó '{no}' inexistente")))?; - self.arvore.nos[idx].metricas.insert(metrica.to_string(), valor); - Ok(()) - } - - fn metrica(&self, no: &str, metrica: &str) -> PyResult { - let idx = self - .arvore - .no(no) - .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(format!("nó '{no}' inexistente")))?; - Ok(self.arvore.nos[idx].metricas.get(metrica).copied().unwrap_or(0.0)) - } - - fn regular(&mut self, origem: &str, delta: f64, lambda: f64) -> PyResult<()> { - let idx = self - .arvore - .no(origem) - .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(format!("nó '{origem}' inexistente")))?; - self.arvore.regular_topo_fundo(idx, delta, lambda); - Ok(()) - } - - fn dump(&self) -> PyResult { - Python::with_gil(|py| { - let nos: Vec = self - .arvore - .nos - .iter() - .map(|no| { - serde_json::json!({ - "nome": no.nome, "ini": no.ini, "fim": no.fim, - "profundidade": no.profundidade, - "metricas": no.metricas, - }) - }) - .collect(); - let d = serde_json::json!({ - "n_nos": self.arvore.n_nos(), - "n_params": self.arvore.n_params, - "invariantes_ok": self.arvore.invariantes_ok().is_ok(), - "nos": nos, - }); - Ok(pythonize::pythonize(py, &d)?.into()) - }) - } -} - -/// Demo/gate da camada FNH sobre o backbone: mede o efeito do gate de -/// punição nas rotações aprendíveis (ablação: π baixa vs π alta). -#[pyfunction] -#[pyo3(signature = (d_in=8, d_out=6, passos=60, seed=42))] -fn demo_fnh(py: Python, d_in: usize, d_out: usize, passos: usize, seed: u64) -> PyResult { - let dev = candle_core::Device::Cpu; - let mut rng = rng::RngDeterministico::novo(seed); - let mut camada = fnh::CamadaFNH::nova(d_in, d_out, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fnh: {e}")))?; - let x: Vec = (0..4 * d_in).map(|_| rng.normal_padrao() as f32).collect(); - let xt = candle_core::Tensor::from_vec(x, (4, d_in), &dev) - .map_err(py_err)?; - camada - .suavizador - .calibrar(camada.base.w.as_tensor(), &xt) - .map_err(py_err)?; - // treino curto com punição ALTA (gate abre) vs BAIXA (gate fechado) - let mut resumo = serde_json::Map::new(); - for (rotulo, punicao) in [("baixa", 0.1f64), ("alta", 0.95)] { - let mut cam = fnh::CamadaFNH::nova(d_in, d_out, &dev).map_err(py_err)?; - // mesma semente de pesos: copia os valores - let _ = &mut cam; - let mut hub = telemetria::TelemetryHub::novo(None); - let mut perdas = Vec::new(); - for p in 0..passos { - // ruído no x a cada passo (regime não-estacionário leve) - let ruido: Vec = (0..4 * d_in) - .map(|_| (rng.normal(0.0, 0.05)) as f32) - .collect(); - let xt2 = (&xt - + &candle_core::Tensor::from_vec(ruido, (4, d_in), &dev).map_err(py_err)?) - .map_err(py_err)?; - let y = cam.forward(&xt2, punicao).map_err(py_err)?; - // alvo: saída da camada base (auto-referência: regular para W) - let z_ref = cam.linear_sem_bias(&xt2, cam.base.w.as_tensor()).map_err(py_err)?; - let perda = (&y).sub(&z_ref).map_err(py_err)?.sqr().map_err(py_err)?.sum_all().map_err(py_err)?; - perdas.push(perda.to_scalar::().map_err(py_err)? as f64); - let grads = perda.backward().map_err(py_err)?; - // gradiente descendente manual simples nos θ das rotações - if let Some(g) = grads.get(&cam.r_wp.theta) { - let gv = g.flatten_all().map_err(py_err)?.to_vec1::().map_err(py_err)?; - let th = cam.r_wp.theta.as_tensor().flatten_all().map_err(py_err)?.to_vec1::().map_err(py_err)?; - let novo: Vec = th.iter().zip(gv.iter()).map(|(t, g_)| t - 0.05 * g_).collect(); - cam.r_wp - .theta - .set(&candle_core::Tensor::from_vec(novo, th.len(), &dev).map_err(py_err)?) - .map_err(py_err)?; - } - hub.registrar("fnh_gate", cam.gate_ultimo); - hub.registrar("fnh_punicao", punicao); - hub.registrar("perda", perdas[p]); - let _ = p; - } - let theta_norma: f64 = cam - .r_wp - .theta - .as_tensor() - .flatten_all() - .map_err(py_err)? - .to_vec1::() - .map_err(py_err)? - .iter() - .map(|v| (*v as f64).powi(2)) - .sum::() - .sqrt(); - let delta_ort = cam.r_wp.erro_ortogonalidade().map_err(py_err)?; - resumo.insert( - format!("punicao_{rotulo}"), - serde_json::json!({ - "gate_final": cam.gate_ultimo, - "theta_norma": theta_norma, - "delta_ort": delta_ort, - "perda_inicial": perdas[0], - "perda_final": perdas[perdas.len() - 1], - }), - ); - } - let _ = &mut camada; - let d = serde_json::json!({ - "versao": "fnh-0.7.0", - "resumo": resumo, - }); - Ok(pythonize::pythonize(py, &d)?.into()) -} - -fn py_err(e: candle_core::Error) -> pyo3::PyErr { - pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")) -} - -/// **Quebra-cabeça sobre o conhecimento TREINADO REAL** (script 63): -/// carrega o tokenizador (codebook do nível `nivel` — o conhecimento -/// aprendido dos dados reais) e o gerador (a cabeça real como logits_fn -/// do verificador); executa o protocolo completo de r-30 Q-AD. -#[pyfunction] -#[pyo3(signature = (cfg, caminho_tokenizador, caminho_gerador, n_pecas=4, n_rodadas=12, tau_c=0.4, nivel=0, seed=777))] -fn quebra_cabeca_real( - cfg: &ConfigPy, - caminho_tokenizador: &str, - caminho_gerador: &str, - n_pecas: usize, - n_rodadas: usize, - tau_c: f64, - nivel: usize, - seed: u64, -) -> PyResult { - Python::with_gil(|py| { - let dev = candle_core::Device::Cpu; - crate::rng::definir_semente(seed); - let mut rng = rng::RngDeterministico::novo(seed); - let mut tok = tokenizador::TokenizadorConceitos::novo(&cfg.cfg, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("tok: {e}")))?; - carregar_tokenizador(&mut tok, caminho_tokenizador) - .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; - let mut ger = gerador::GeradorConceitos::novo(&cfg.cfg, &dev) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("ger: {e}")))?; - ger.carregar(caminho_gerador) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("ger carregar: {e}")))?; - let nivel = nivel.min(tok.quants.len().saturating_sub(1)); - // conhecimento treinado REAL: codebook Stiefel (K, d_lat) - let codebook = tok - .quants[nivel] - .codebook() - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("codebook: {e}")))?; - let d_lat = codebook.dims()[1]; - let k = ger.k; - // δ_ort REAL do codebook treinado (rotação Stiefel em treino — A2) - let delta_ort_codebook = tok.quants[nivel] - .delta_ort() - .unwrap_or(1.0); - // logits_fn REAL: agregado (d_lat,) → proj_entrada → norm → cabeça - let proj = &ger.proj_entrada; - let norm = &ger.norm_entrada; - let cabeca = &ger.cabeca.cabecas[nivel]; - let logits_fn = move |x: &candle_core::Tensor| -> candle_core::Result { - let h = proj.fwd(&x.reshape((1, d_lat))?)?; - let h = norm.fwd(&h)?; - let lg = cabeca.fwd(&h)?; - lg.reshape((k,)) - }; - // verificador com temperatura calibrada pela fase C quando - // disponível; default 1,0 (a cota V-2 aplica-se com ECE medido) - let verif = verificador::VerificadorNcm::novo(); - let mut arvore = arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new()); - let cfg_qc = pensamento::ConfigQuebraCabeca::novo(n_pecas, n_rodadas, tau_c); - let r = pensamento::quebra_cabeca_inovar(&codebook, &verif, &logits_fn, &mut arvore, &cfg_qc, &mut rng) - .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("qc: {e}")))?; - let rodadas: Vec = r - .rodadas - .iter() - .map(|x| { - serde_json::json!({ - "rodada": x.rodada, "confianca": x.confianca, - "aceito": x.aceito, "delta": x.delta_aplicado, - }) - }) - .collect(); - let d_ = serde_json::json!({ - "versao": "quebra-cabeca-real-0.7.0", - "nivel": nivel, - "delta_ort_codebook": delta_ort_codebook, - "n_conceitos": codebook.dims()[0], - "d_lat": d_lat, - "aceitos": r.aceitos, - "rejeitados": r.rejeitados, - "confianca_base": r.confianca_base, - "confianca_melhor": r.confianca_melhor, - "telemetria": r.telemetria, - "rodadas": rodadas, - "arvore_invariantes": arvore.invariantes_ok().is_ok(), - "punicao_no_pensamento": arvore - .no("pensamento") - .and_then(|i| arvore.nos[i].metricas.get("punicao").copied()) - .unwrap_or(0.0), - }); - Ok(pythonize::pythonize(py, &d_)?.into()) - }) -} - -/// Demo/gate do quebra-cabeça sobre conceitos SINTÉTICOS estruturados -/// (validação de mecanismo; a avaliação com dados reais vive no script 63). -#[pyfunction] -#[pyo3(signature = (m=24, d=8, n_pecas=4, n_rodadas=12, tau_c=0.35, seed=777))] -fn demo_quebra_cabeca( - py: Python, - m: usize, - d: usize, - n_pecas: usize, - n_rodadas: usize, - tau_c: f64, - seed: u64, -) -> PyResult { - let dev = candle_core::Device::Cpu; - let mut rng = rng::RngDeterministico::novo(seed); - // conceitos estruturados: cluster dominante +1 (média direcional) com - // 25% de outliers fracos — o agregado TEM direção (sem cancelamento) - let mut v = Vec::with_capacity(m * d); - for i in 0..m { - let centro = if i < m / 4 { 0.2 } else { 1.0 }; - for j in 0..d { - v.push((centro + rng.normal(0.0, 0.25)) as f32); - } - } - let conceitos = candle_core::Tensor::from_vec(v, (m, d), &dev).map_err(py_err)?; - // logits_fn ASSIMÉTRICO: projeção fixa W_proj (rotação-sensível — a - // agregação simétrica seria invariante sob permutação/rotação, e o - // backbone real é exatamente o avaliador NÃO-invariente da prática). - let mut wv = Vec::with_capacity(d * 4); - for _ in 0..d * 4 { - wv.push((rng.normal_padrao() as f32) * 2.5); - } - let w_proj = candle_core::Tensor::from_vec(wv, (d, 4), &dev).map_err(py_err)?; - // verificador com temperatura padrão - let verif = verificador::VerificadorNcm::novo(); - let logits_fn = |x: &candle_core::Tensor| -> candle_core::Result { - // logits = xᵀ·W_proj (4,) — sensível à direção do agregado - x.reshape((1, d))?.matmul(&w_proj)?.reshape((4,)) - }; - let mut arvore = arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new()); - let cfg = pensamento::ConfigQuebraCabeca::novo(n_pecas, n_rodadas, tau_c); - let r = pensamento::quebra_cabeca_inovar(&conceitos, &verif, &logits_fn, &mut arvore, &cfg, &mut rng) - .map_err(py_err)?; - let rodadas: Vec = r - .rodadas - .iter() - .map(|x| { - serde_json::json!({ - "rodada": x.rodada, "confianca": x.confianca, - "aceito": x.aceito, "delta": x.delta_aplicado, - }) - }) - .collect(); - let d_ = serde_json::json!({ - "versao": "quebra-cabeca-0.7.0", - "aceitos": r.aceitos, - "rejeitados": r.rejeitados, - "confianca_base": r.confianca_base, - "confianca_melhor": r.confianca_melhor, - "telemetria": r.telemetria, - "rodadas": rodadas, - "arvore_invariantes": arvore.invariantes_ok().is_ok(), - }); - Ok(pythonize::pythonize(py, &d_)?.into()) -} - -/// Demo/gate das unidades NLP/NLG sobre estados ocultos sintéticos -/// (nascimento neutro + gates + coerência de bigramas + especulativo). -#[pyfunction] -#[pyo3(signature = (d=32, t=6, seed=11))] -fn demo_nlp_nlg(py: Python, d: usize, t: usize, seed: u64) -> PyResult { - let dev = candle_core::Device::Cpu; - let mut rng = rng::RngDeterministico::novo(seed); - let v: Vec = (0..2 * t * d).map(|_| rng.normal_padrao() as f32).collect(); - let h = candle_core::Tensor::from_vec(v, (2, t, d), &dev).map_err(py_err)?; - // NLP: enriquecimento aditivo — neutro no nascimento - let mut nlp = nlp::UnidadeNlp::nova(d, &dev).map_err(|e| { - pyo3::exceptions::PyRuntimeError::new_err(format!("nlp (d%4): {e}")) - })?; - let h_enr = nlp.enriquecer(&h).map_err(py_err)?; - let dif_nlp = (&h).sub(&h_enr).map_err(py_err)?.abs().map_err(py_err)?.max_all().map_err(py_err)?.to_scalar::().map_err(py_err)? as f64; - // intenção - let (intencoes, confs) = nlp.intencao_argmax(&h).map_err(py_err)?; - // NLG: plano + condicionamento neutro + coerência - let mut nlg = nlg::UnidadeNlg::nova(d, 4096, &dev).map_err(py_err)?; - let plano = nlg.plano(&h).map_err(py_err)?; - let p8 = candle_core::Tensor::randn(0f32, 1f32, (nlg::N_EIXOS_ESTILO,), &dev).map_err(py_err)?; - let p01 = candle_nn::ops::sigmoid(&p8).map_err(py_err)?; - let h_cond = nlg.condicionar(&h, Some(&p01)).map_err(py_err)?; - let dif_nlg = (&h).sub(&h_cond).map_err(py_err)?.abs().map_err(py_err)?.max_all().map_err(py_err)?.to_scalar::().map_err(py_err)? as f64; - // coerência: contexto observado vs candidato novo - let ctx = vec![5u32, 6, 7, 8, 9]; - nlg.observar_contexto(&ctx); - let s_mesmo = nlg.score_coerencia(&ctx); - let s_novo = nlg.score_coerencia(&[50u32, 51, 52, 53]); - // especulativo: aceitação típica + stats - let mut espec = nlg::DecodificadorEspeculativo::novo(4, 0.95, 10); - let logits = candle_core::Tensor::from_vec(vec![3.0f32, 0.2, 0.1, 0.05], (4,), &dev) - .map_err(py_err)?; - let mut aceites = 0usize; - for _ in 0..30 { - let (_, ok) = espec.verificar_e_corrigir(&logits, &[0, 1]).map_err(py_err)?; - if ok { - aceites += 1; - } - } - let d_ = serde_json::json!({ - "versao": "nlp-nlg-0.7.0", - "nlp_gate_medio": nlp.gate_medio, - "nlp_dif_nascimento": dif_nlp, - "nlp_intencoes": intencoes, - "nlp_confiancas": confs, - "nlg_plano_forma": plano.dims(), - "nlg_dif_nascimento": dif_nlg, - "nlg_coerencia_mesmo": s_mesmo, - "nlg_coerencia_novo": s_novo, - "espec_aceitacao_media": espec.stats.aceitacao_media, - "espec_speedup": espec.stats.speedup_estimado, - "espec_aceites_30": aceites, - "gates": { - "nlp_neutro": dif_nlp == 0.0, - "nlg_neutro": dif_nlg == 0.0, - "gate_medio_ok": (nlp.gate_medio - 0.119).abs() < 0.05, - "coerencia_ok": s_mesmo > 0.9 && s_novo < 0.1, - "espec_ok": espec.stats.aceitacao_media > 0.8, - }, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) -} - -/// Demo/gate do auto-ajuste SOM com tetos (S-AB/S-SL). -#[pyfunction] -fn demo_autoajuste_som(py: Python) -> PyResult { - let mut a = controlador::AutoAjusteSom::novo(0.5, 1.0); - let (al_qe, _) = a.passo(1.0, 0.0, 0.0); - let (al_cross, sg_cross) = a.passo(0.0, 0.0, 1.0); - let (al_max, sg_max) = a.passo(1.0, 1.0, 1.0); - let mut parou_5chunks = false; - for _ in 0..5 { - parou_5chunks = a.avaliar_parada(0.95, 0.1, 0.1, false, true, false, false); - } - let d_ = serde_json::json!({ - "versao": "som-autoadjust-0.7.0", - "alfa_qe_alta": al_qe, "alfa_crossing": al_cross, "sigma_crossing": sg_cross, - "alfa_max": al_max, "sigma_max": sg_max, - "parou_5chunks": parou_5chunks, - "motivo": a.motivo_parada, - "gates": { - "tetos_ok": al_max <= 1.0 + 1e-9 && sg_max <= 2.0 + 1e-9, - "piso_ok": al_cross >= 0.5 - 1e-9, - "qe_sobe": al_qe > 0.5, - "stop_dispara": parou_5chunks, - }, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) -} - -/// **v0.8.0 — análise/prova/teste das 4 propostas Qwen3.8** (r-33): mede -/// custo e exatidão de (1) Newton analítico vs golden-section (verificador), -/// (2) Cayley vs polar (FNH), (3) árvore pós-ordem iterativa vs recursiva, -/// (4) MoE vetorizada vs referência (valores + gradiente restaurado — bug -/// v080-1), e re-executa a ablação JEPA de 3 braços com a correção. -#[pyfunction] -#[allow(deprecated)] // mede o bug v080-1 intencionalmente (fwd = oráculo) -fn demo_qwen_v080(py: Python) -> PyResult { - use candle_core::{Device, Tensor}; - use std::time::Instant; - let dev = Device::Cpu; - - // ---------- (1) Newton vs golden-section (verificador) ---------- - let mut rng = crate::rng::RngDeterministico::novo(808); - let (n, k) = (1024usize, 32usize); - let mut lv = Vec::with_capacity(n * k); - let mut av = Vec::with_capacity(n); - for _ in 0..n { - let alvo = rng.inteiro(k) as u32; - av.push(alvo); - for j in 0..k { - let base = if j as u32 == alvo { 3.0 } else { 0.0 }; - lv.push(base + rng.normal(0.0, 1.0) as f32); - } - } - let lt = Tensor::from_vec(lv, (n, k), &dev).unwrap(); - let at = Tensor::from_vec(av, (n,), &dev).unwrap(); - let t0 = Instant::now(); - let (t_g, _, nll_g) = verificador::calibrar_temperatura(<, &at).unwrap(); - let tempo_golden = t0.elapsed().as_secs_f64(); - let t0 = Instant::now(); - let (t_n, _, nll_n, n_aval) = - verificador::calibrar_temperatura_newton(<, &at, 40).unwrap(); - let tempo_newton = t0.elapsed().as_secs_f64(); - - // ---------- (2) Cayley vs polar (FNH) ---------- - let n_rot = 16usize; - let r = fnh::RotacaoSkew::nova(n_rot, &dev).unwrap(); - let th: Vec = (0..n_rot * (n_rot - 1) / 2) - .map(|i| ((i as f32) * 0.11) % 0.5) - .collect(); - r.theta - .set(&Tensor::from_vec(th, (n_rot * (n_rot - 1) / 2,), &dev).unwrap()) - .unwrap(); - let rep = 25usize; - let t0 = Instant::now(); - let mut delta_polar = 0.0; - for _ in 0..rep { - delta_polar = crate::stiefel::erro_ortogonalidade(&r.rotacao_polar().unwrap()).unwrap(); - } - let tempo_polar = t0.elapsed().as_secs_f64() / rep as f64; - let t0 = Instant::now(); - let mut delta_cayley = 0.0; - for _ in 0..rep { - delta_cayley = crate::stiefel::erro_ortogonalidade(&r.rotacao_cayley().unwrap()).unwrap(); - } - let tempo_cayley = t0.elapsed().as_secs_f64() / rep as f64; - - // ---------- (3) árvore iterativa vs recursiva ---------- - let mut arv = arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new()); - let mut pai = 1usize; - for i in 0..20_000u32 { - pai = arv.anexar_filho(pai, &format!("c{i}"), 1); - } - arv.nos[pai].metricas.insert("m".into(), 1.0); - let t0 = Instant::now(); - let total_it = arv.agregar_fundo_topo("m"); - let tempo_arv_it = t0.elapsed().as_secs_f64(); - // repõe TODAS as métricas locais (a agregação sobrescreveu com os - // agregados) — só a folha tem valor; o resto volta a 0 — e roda a - // recursiva (profundidade 20k ok na thread principal do Python) - for no in arv.nos.iter_mut() { - no.metricas.insert("m".into(), 0.0); - } - arv.nos[pai].metricas.insert("m".into(), 1.0); - let t0 = Instant::now(); - let total_rec = arv.agregar_fundo_topo_rec("m"); - let tempo_arv_rec = t0.elapsed().as_secs_f64(); - - // ---------- (4) MoE vetorizada: valores + gradiente + custo ---------- - let (nm, dm, em) = (256usize, 32usize, 8usize); - let xv: Vec = (0..nm * dm).map(|_| rng.normal(0.0, 1.0) as f32).collect(); - let x = Tensor::from_vec(xv, (nm, dm), &dev).unwrap(); - let moe = moe_redirecionavel::MoeRedirecionavel::novo(dm, em, 2, nm, &dev).unwrap(); - let s_ref = moe.fwd(&x, moe_redirecionavel::Papel::Codificador).unwrap(); - let s_vet = moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap(); - let dif_moe = (&s_ref.saida - &s_vet.saida) - .unwrap() - .abs() - .unwrap() - .max_all() - .unwrap() - .to_scalar::() - .unwrap() as f64; - let rep_moe = 10usize; - let t0 = Instant::now(); - for _ in 0..rep_moe { - let _ = moe.fwd(&x, moe_redirecionavel::Papel::Codificador).unwrap(); - } - let tempo_moe_ref = t0.elapsed().as_secs_f64() / rep_moe as f64; - let t0 = Instant::now(); - for _ in 0..rep_moe { - let _ = moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap(); - } - let tempo_moe_vet = t0.elapsed().as_secs_f64() / rep_moe as f64; - // normas de gradiente (bug v080-1: fwd = 0; vetorizada > 0) - let norma = |vet: bool| -> (f64, f64) { - let s = if vet { - moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap() - } else { - moe.fwd(&x, moe_redirecionavel::Papel::Codificador).unwrap() - }; - let perda = s.saida.sqr().unwrap().sum_all().unwrap(); - let gs = perda.backward().unwrap(); - let mut nr = 0.0; - for v in moe.roteador.vars() { - if let Some(g) = gs.get(&v) { - nr += g.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64; - } - } - let mut ne = 0.0; - for e in &moe.especialistas { - for v in e.vars() { - if let Some(g) = gs.get(&v) { - ne += g.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64; - } - } - } - (nr, ne) - }; - let (grad_ref_r, grad_ref_e) = norma(false); - let (grad_vet_r, grad_vet_e) = norma(true); - - // ---------- (5) re-ablação JEPA 3 braços (gradiente restaurado) ---------- - let xv: Vec = (0..64 * 12).map(|_| rng.normal(0.0, 1.0) as f32).collect(); - let xj = Tensor::from_vec(xv, (64, 12), &dev).unwrap(); - let mut ablac = serde_json::Map::new(); - for modo in ["pequena", "densa", "moe"] { - let rj = moe_redirecionavel::treinar_jepa_com_moe(&xj, modo, 120, 0.5, 42).unwrap(); - ablac.insert( - modo.into(), - serde_json::json!({ - "perda_inicial": rj.perda_pred_inicial, - "perda_final": rj.perda_pred_final, - "queda_pct": 100.0 * (1.0 - rj.perda_pred_final / rj.perda_pred_inicial.max(1e-9)), - }), - ); - } - - let d_ = serde_json::json!({ - "versao": "qwen-v080-1.0", - "newton_vs_golden": { - "t_golden": t_g, "t_newton": t_n, "dif_t": (t_g - t_n).abs(), - "nll_golden": nll_g, "nll_newton": nll_n, - "nll_dif": (nll_g - nll_n).abs(), - "n_aval_newton": n_aval, - "tempo_golden_s": tempo_golden, "tempo_newton_s": tempo_newton, - "speedup": tempo_golden / tempo_newton.max(1e-9), - "gates": { - "mesmo_t": (t_g - t_n).abs() < 1e-3, - "nll_nao_piora": nll_n <= nll_g + 1e-4, - "menos_avaliacoes": n_aval < 36, - }, - }, - "cayley_vs_polar": { - "delta_ort_polar": delta_polar, "delta_ort_cayley": delta_cayley, - "tempo_polar_s": tempo_polar, "tempo_cayley_s": tempo_cayley, - "speedup": tempo_polar / tempo_cayley.max(1e-9), - "gates": { - "ambos_exatos": delta_polar < 1e-6 && delta_cayley < 1e-6, - "cayley_mais_rapido": tempo_cayley < tempo_polar, - }, - }, - "arvore_iter_vs_rec": { - "n_nos": arv.n_nos(), - "total_iter": total_it, "total_rec": total_rec, - "bit_a_bit": total_it.to_bits() == total_rec.to_bits(), - "tempo_iter_s": tempo_arv_it, "tempo_rec_s": tempo_arv_rec, - "gates": { "iguais_bit_a_bit": total_it == total_rec }, - }, - "moe_vetorizada": { - "max_dif_valores": dif_moe, - "tempo_ref_s": tempo_moe_ref, "tempo_vet_s": tempo_moe_vet, - "grad_roteador_ref": grad_ref_r, "grad_especialistas_ref": grad_ref_e, - "grad_roteador_vet": grad_vet_r, "grad_especialistas_vet": grad_vet_e, - "gates": { - "valores_iguais": dif_moe < 1e-4, - "bug_v080_1_confirmado": grad_ref_r == 0.0 && grad_ref_e == 0.0, - "gradiente_restaurado": grad_vet_r > 0.0 && grad_vet_e > 0.0, - }, - }, - "ablacao_jepa_3_bracos_v080": ablac, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) -} - -/// **v0.9.0 — veredito executável das críticas Qwen3.8 (r-34):** mede os -/// aprimoramentos contra as referências v0.8.0 (oráculos preservados): -/// (1) skew O(n²) via index_add — bit-a-bit + tempo + memória analítica; -/// (2) calibrar vetorial — bit-a-bit em 3 passos EMA + tempo; -/// (3) forward canônico (ex-fwd_vetorizado) — regressão de valores e -/// gradiente; -/// (4) newtypes de domínio — rejeição de fora-de-domínio. -#[pyfunction] -fn demo_criticas_v090(py: Python) -> PyResult { - use candle_core::{Device, Tensor}; - use std::time::Instant; - let dev = Device::Cpu; - let mut rng = crate::rng::RngDeterministico::novo(909); - - // ---------- (1) skew: index_add O(n²) vs matmul de seleção O(n⁴) ---------- - let mut skew_rel = serde_json::Map::new(); - let mut bits_todos = true; - for &n in &[16usize, 64, 128] { - let r = fnh::RotacaoSkew::nova(n, &dev).unwrap(); - let m = n * (n - 1) / 2; - let th: Vec = (0..m).map(|_| rng.uniforme_intervalo(-0.9, 0.9) as f32).collect(); - r.theta.set(&Tensor::from_vec(th, (m,), &dev).unwrap()).unwrap(); - // bit-a-bit (F-13) - let an = r.skew().unwrap().flatten_all().unwrap().to_vec1::().unwrap(); - let ar = r.skew_matmul_referencia().unwrap().flatten_all().unwrap().to_vec1::().unwrap(); - let bits_iguais = an.iter().zip(ar.iter()).all(|(a, b)| a.to_bits() == b.to_bits()); - bits_todos &= bits_iguais; - // tempo (referência esquentada 1×; média de 5) - let _ = r.skew_matmul_referencia().unwrap(); - let t0 = Instant::now(); - for _ in 0..5 { let _ = r.skew_matmul_referencia().unwrap(); } - let t_ref = t0.elapsed().as_secs_f64() / 5.0; - let _ = r.skew().unwrap(); - let t0 = Instant::now(); - for _ in 0..5 { let _ = r.skew().unwrap(); } - let t_novo = t0.elapsed().as_secs_f64() / 5.0; - let bytes_ref = (m * n * n * 4) as f64; - let bytes_novo = ((n * n + 3 * m) * 4 + 2 * m * 4) as f64; - skew_rel.insert( - format!("n_{n}"), - serde_json::json!({ - "tempo_ref_s": t_ref, "tempo_novo_s": t_novo, - "speedup": t_ref / t_novo.max(1e-12), - "bytes_ref_selecao": bytes_ref, "bytes_novo": bytes_novo, - "razao_memoria": bytes_ref / bytes_novo, - "bits_iguais": bits_iguais, - }), - ); - } - - // ---------- (2) calibrar: tensor vs laços nativos ---------- - let (d_out, d_in, n_lin) = (512usize, 512usize, 4096usize); - let wv: Vec = (0..d_out * d_in).map(|_| rng.uniforme_intervalo(-2.0, 2.0) as f32).collect(); - let w = Tensor::from_vec(wv, (d_out, d_in), &dev).unwrap(); - let xv: Vec = (0..n_lin * d_in).map(|_| rng.uniforme_intervalo(-3.0, 3.0) as f32).collect(); - let x = Tensor::from_vec(xv, (n_lin, d_in), &dev).unwrap(); - let mut s_novo = fnh::SuavizadorQuant::novo(0.5, 0.98); - let mut s_tens = fnh::SuavizadorQuant::novo(0.5, 0.98); - let mut s_ref = fnh::SuavizadorQuant::novo(0.5, 0.98); - let mut bits_cal = true; - for _ in 0..3 { - s_novo.calibrar(&w, &x).unwrap(); - s_tens.calibrar_tensor(&w, &x).unwrap(); - s_ref.calibrar_referencia(&w, &x).unwrap(); - for (a, b) in s_novo.w_max.as_ref().unwrap().iter().zip(s_ref.w_max.as_ref().unwrap()) { - bits_cal &= a.to_bits() == b.to_bits(); - } - for (a, b) in s_novo.x_max.as_ref().unwrap().iter().zip(s_ref.x_max.as_ref().unwrap()) { - bits_cal &= a.to_bits() == b.to_bits(); - } - } - // tempo (esquentadas 1×; média de 5) - let _ = s_ref.calibrar_referencia(&w, &x); - let t0 = Instant::now(); - for _ in 0..5 { s_ref.calibrar_referencia(&w, &x).unwrap(); } - let t_cal_ref = t0.elapsed().as_secs_f64() / 5.0; - let _ = s_novo.calibrar(&w, &x); - let t0 = Instant::now(); - for _ in 0..5 { s_novo.calibrar(&w, &x).unwrap(); } - let t_cal_novo = t0.elapsed().as_secs_f64() / 5.0; - let _ = s_tens.calibrar_tensor(&w, &x); - let t0 = Instant::now(); - for _ in 0..5 { s_tens.calibrar_tensor(&w, &x).unwrap(); } - let t_cal_tens = t0.elapsed().as_secs_f64() / 5.0; - - // ---------- (3) forward: regressão de valores + gradiente ---------- - let (nm, dm, em) = (256usize, 32usize, 8usize); - let xv2: Vec = (0..nm * dm).map(|_| rng.normal(0.0, 1.0) as f32).collect(); - let x2 = Tensor::from_vec(xv2, (nm, dm), &dev).unwrap(); - #[allow(deprecated)] // oráculo do bug v080-1 (r-33 §5) - let moe = moe_redirecionavel::MoeRedirecionavel::novo(dm, em, 2, nm, &dev).unwrap(); - #[allow(deprecated)] - let s_refm = moe.fwd(&x2, moe_redirecionavel::Papel::Codificador).unwrap(); - let s_fwd = moe.forward(&x2, moe_redirecionavel::Papel::Codificador).unwrap(); - let dif_moe = (&s_refm.saida - &s_fwd.saida).unwrap().abs().unwrap() - .max_all().unwrap().to_scalar::().unwrap() as f64; - let perda = s_fwd.saida.sqr().unwrap().sum_all().unwrap(); - let gs = perda.backward().unwrap(); - let mut gr = 0.0f64; - for v in moe.roteador.vars() { - if let Some(g) = gs.get(&v) { - gr += g.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64; - } - } - let mut ge = 0.0f64; - for e in &moe.especialistas { - for v in e.vars() { - if let Some(g) = gs.get(&v) { - ge += g.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64; - } - } - } - - // ---------- (4) newtypes de domínio (T-V1) ---------- - let rejeita = crate::tipos::Probabilidade::novo(7.0).is_err() - && crate::tipos::Probabilidade::novo(-0.5).is_err() - && crate::tipos::Probabilidade::novo(f64::NAN).is_err() - && crate::tipos::GateAtivacao::novo(1.5).is_err(); - let aceita = crate::tipos::Probabilidade::novo(0.37).unwrap().valor() == 0.37; - let gate_tipado = fnh::GatePunicao::default() - .valor_tipado(&crate::tipos::Probabilidade::novo(0.9).unwrap()) - .valor(); - - let d_ = serde_json::json!({ - "versao": "criticas-v090-1.0", - "skew_index_add": { - "por_n": skew_rel, - "gates": { - "bits_iguais_todos_n": bits_todos, - }, - }, - "calibrar_zero_copia": { - "bits_iguais_3_passos": bits_cal, - "tempo_ref_s": t_cal_ref, "tempo_novo_s": t_cal_novo, - "tempo_tensor_s": t_cal_tens, - "speedup_zero_copia": t_cal_ref / t_cal_novo.max(1e-12), - "razao_tensor_vs_zero_copia": t_cal_tens / t_cal_novo.max(1e-12), - "gates": { "bit_a_bit": bits_cal, "nao_regride": t_cal_novo <= t_cal_ref * 1.05 }, - }, - "forward_canonico": { - "max_dif_valores_vs_oraculo": dif_moe, - "grad_roteador": gr, "grad_especialistas": ge, - "gates": { - "valores_iguais": dif_moe < 1e-4, - "gradiente_ativo": gr > 0.0 && ge > 0.0, - }, - }, - "newtypes_dominio": { - "rejeita_fora_do_dominio": rejeita, - "aceita_dentro": aceita, - "gate_tipado_em_0_9": gate_tipado, - "gates": { "fronteira_validada": rejeita && aceita }, - }, - }); - Ok(pythonize::pythonize(py, &d_)?.into()) -} - -#[pymodule] -fn nucleo_rust(m: &Bound<'_, PyModule>) -> PyResult<()> { - pyo3_log::init(); - m.add_function(wrap_pyfunction!(versao, m)?)?; - m.add_function(wrap_pyfunction!(info_build, m)?)?; - m.add_function(wrap_pyfunction!(lint_config, m)?)?; - m.add_function(wrap_pyfunction!(treinar_fase_a, m)?)?; - m.add_function(wrap_pyfunction!(treinar_fase_b, m)?)?; - m.add_function(wrap_pyfunction!(gerar_amostras, m)?)?; - m.add_function(wrap_pyfunction!(verificar_mascara, m)?)?; - m.add_function(wrap_pyfunction!(verificar_stiefel, m)?)?; - // v0.5.0 — SOM 4D / JEPA / Pensamento - m.add_function(wrap_pyfunction!(treinar_som4d, m)?)?; - m.add_function(wrap_pyfunction!(treinar_gng, m)?)?; - m.add_function(wrap_pyfunction!(treinar_hsom, m)?)?; - m.add_function(wrap_pyfunction!(demo_tsom, m)?)?; - m.add_function(wrap_pyfunction!(treinar_jepa, m)?)?; - m.add_function(wrap_pyfunction!(treinar_lejepa, m)?)?; - m.add_function(wrap_pyfunction!(treinar_mcjepa, m)?)?; - m.add_function(wrap_pyfunction!(treinar_bjepa, m)?)?; - m.add_function(wrap_pyfunction!(demo_sonic_moe, m)?)?; - m.add_function(wrap_pyfunction!(demo_replay_ssm, m)?)?; - m.add_function(wrap_pyfunction!(classificar_erro, m)?)?; - m.add_function(wrap_pyfunction!(confianca_de_logits, m)?)?; - m.add_function(wrap_pyfunction!(projetar_ridge, m)?)?; - m.add_function(wrap_pyfunction!(demo_pensamento, m)?)?; - m.add_function(wrap_pyfunction!(patches_2d, m)?)?; - m.add_function(wrap_pyfunction!(patches_serie, m)?)?; - m.add_function(wrap_pyfunction!(grupos_pontos, m)?)?; - // v0.6.0 - m.add_function(wrap_pyfunction!(calibrar_verificador, m)?)?; - m.add_function(wrap_pyfunction!(demo_verificador_ncm, m)?)?; - m.add_function(wrap_pyfunction!(treinar_fase_c, m)?)?; - m.add_function(wrap_pyfunction!(demo_moe_redirecionavel, m)?)?; - m.add_function(wrap_pyfunction!(jepa_com_moe, m)?)?; - m.add_function(wrap_pyfunction!(som_com_moe, m)?)?; - m.add_function(wrap_pyfunction!(demo_acoplador_duplo, m)?)?; - m.add_function(wrap_pyfunction!(demo_pensamento_ncm, m)?)?; - m.add_function(wrap_pyfunction!(demo_autoajuste, m)?)?; - m.add_function(wrap_pyfunction!(demo_fnh, m)?)?; - m.add_function(wrap_pyfunction!(demo_quebra_cabeca, m)?)?; - m.add_function(wrap_pyfunction!(quebra_cabeca_real, m)?)?; - m.add_function(wrap_pyfunction!(demo_nlp_nlg, m)?)?; - m.add_function(wrap_pyfunction!(demo_autoajuste_som, m)?)?; - // v0.8.0 — análise/prova/teste das propostas Qwen3.8 - m.add_function(wrap_pyfunction!(demo_qwen_v080, m)?)?; - // v0.9.0 — veredito executável das críticas Qwen3.8 (r-34) - m.add_function(wrap_pyfunction!(demo_criticas_v090, m)?)?; - m.add_class::()?; - m.add_class::()?; - m.add_class::()?; - #[cfg(feature = "ponte_async")] - m.add_class::()?; - m.add_class::()?; - Ok(()) -} diff --git a/src/python/calibracao.rs b/src/python/calibracao.rs new file mode 100644 index 0000000000000000000000000000000000000000..6e3b3fe17b084983d885febe7b1edae3192b265b --- /dev/null +++ b/src/python/calibracao.rs @@ -0,0 +1,134 @@ +//! `python::calibracao` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +/// **V-1..V-3:** calibra o verificador NCM em logits retidos e devolve +/// temperatura, ECE antes/depois, NLL antes/depois e o τ_c por quantil DKW. +#[pyfunction] +pub(crate) fn calibrar_verificador( + logits: Vec>, + alvos: Vec, + epsilon: f64, +) -> PyResult { + Python::with_gil(|py| { + let dev = candle_core::Device::Cpu; + let (n, k) = (logits.len(), logits.first().map(|l| l.len()).unwrap_or(1)); + let flat: Vec = logits.into_iter().flatten().collect(); + let lt = candle_core::Tensor::from_vec(flat, (n, k), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let at = candle_core::Tensor::from_vec( + alvos.iter().map(|&a| a as u32).collect::>(), (n,), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut v = verificador::VerificadorNcm::novo(); + let (e_antes, e_depois) = v.ajustar(<, &at) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let confs: Vec = verificador::confianca_por_linha(<) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? + .flatten_all().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? + .to_vec1::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? + .iter().map(|x| *x as f64).collect(); + let tau_c = verificador::limiar_por_erro(&confs, epsilon); + let d_ = serde_json::json!({ + "temperatura": v.temperatura, "ece_antes": e_antes, "ece_depois": e_depois, + "nll_antes": v.n_ll_antes, "nll_depois": v.n_ll_depois, + "tau_c": tau_c, "epsilon": epsilon, "n": n, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) + }) +} + + +/// **Demo do verificador NCM substituindo o oráculo** (r-21 §3.1): treina +/// um classificador pequeno, calibra T* em dados retidos, mede ECE, τ_c +/// (DKW) e a soundness empírica `P(erro|aceito) ≤ 1−τ_c+ECE`; compara com +/// o ORÁCULO (que usa o alvo — impossível em produção) no mesmo problema. +#[pyfunction] +pub(crate) fn demo_verificador_ncm(n: usize, k: usize, semente: u64) -> PyResult { + Python::with_gil(|py| { + let dev = candle_core::Device::Cpu; + crate::rng::definir_semente(semente); + let mut rng = crate::rng::RngDeterministico::novo(semente); + // dados sintéticos com DRIFT (treino ≠ consulta — força OOD leve) + let gerar = |rng: &mut crate::rng::RngDeterministico, m: usize, desloc: f64| + -> (candle_core::Tensor, Vec) { + let mut l = Vec::with_capacity(m * k); + let mut a = Vec::with_capacity(m); + for _ in 0..m { + let alvo = rng.inteiro(k); + a.push(alvo); + for j in 0..k { + let base = if j == alvo { 2.5 } else { 0.0 }; + l.push((base + rng.normal(0.0, 1.2) as f32 + desloc as f32) as f32); + } + } + let lt = candle_core::Tensor::from_vec(l, (m, k), &dev).unwrap(); + (lt, a) + }; + let (lt_tre, alvos_tre) = gerar(&mut rng, n, 0.0); + let (lt_val, alvos_val) = gerar(&mut rng, n / 2, 0.3); + // mini-classificador: matriz W (k, k) treinada por softmax-CE + let w = candle_core::Tensor::randn(0f32, 0.1, (k, k), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let w_var = candle_core::Var::from_tensor(&w) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let at_t = candle_core::Tensor::from_vec( + alvos_tre.iter().map(|&a| a as u32).collect::>(), (n,), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut otim = treino::AdamWManual::novo(vec![w_var.clone()], 0.05, 0.0); + for _ in 0..120 { + let logits = lt_tre.matmul(&w_var.as_tensor().transpose(0, 1).unwrap()).unwrap(); + let ce = candle_nn::loss::cross_entropy(&logits, &at_t).unwrap(); + let g = treino::grads_achatados(&ce, &[w_var.clone()]).unwrap(); + otim.passo(g); + } + // logits de validação com o modelo treinado + let logits_val = lt_val.matmul(&w_var.as_tensor().transpose(0, 1).unwrap()).unwrap(); + let at_v = candle_core::Tensor::from_vec( + alvos_val.iter().map(|&a| a as u32).collect::>(), (n / 2,), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + // calibração (V-1) + soundness (V-2) + quantil (V-3) + let mut v = verificador::VerificadorNcm::novo(); + let (ece_antes, ece_depois) = v.ajustar(&logits_val, &at_v) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let confs: Vec = v.confianca(&logits_val).unwrap() + .flatten_all().unwrap().to_vec1::().unwrap().iter().map(|x| *x as f64).collect(); + let corretos: Vec = logits_val.argmax(candle_core::D::Minus1).unwrap() + .eq(&at_v).unwrap().to_dtype(candle_core::DType::U32).unwrap() + .flatten_all().unwrap().to_vec1::().unwrap() + .iter().map(|x| *x == 1).collect(); + let tau_c = verificador::limiar_por_erro(&confs, 0.25); + let (mut erro_aceito, mut n_aceito) = (0usize, 0usize); + for (c, ok) in confs.iter().zip(corretos.iter()) { + if *c >= tau_c { + n_aceito += 1; + if !ok { + erro_aceito += 1; + } + } + } + let p_erro = erro_aceito as f64 / n_aceito.max(1) as f64; + let cota = (1.0 - tau_c) + ece_depois + 0.06; + // ORÁCULO no mesmo problema (usa o alvo — só para contraste) + let acerto_oraculo = corretos.iter().filter(|c| **c).count() as f64 / corretos.len() as f64; + let d_ = serde_json::json!({ + "versao": verificador::versao(), "n": n, "k": k, + "temperatura_calibrada": v.temperatura, + "ece_antes": ece_antes, "ece_depois": ece_depois, + "nll_antes": v.n_ll_antes, "nll_depois": v.n_ll_depois, + "tau_c_dkw": tau_c, + "p_erro_aceito": p_erro, "cota_soundness": cota, + "soundness_ok": p_erro <= cota, + "calibracao_melhorou": ece_depois < ece_antes, + "acerto_oraculo_referencia": acerto_oraculo, + "verificador_sem_oraculo": true, + "gates": { + "gate_v1_calibracao": ece_depois < ece_antes, + "gate_v2_soundness": p_erro <= cota, + "gate_v3_tau_positivo": tau_c > 0.0 && tau_c < 1.0, + }, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) + }) +} diff --git a/src/python/demos_qwen.rs b/src/python/demos_qwen.rs new file mode 100644 index 0000000000000000000000000000000000000000..059836541359dc90bc3ee1c98121dc4499c0a29b --- /dev/null +++ b/src/python/demos_qwen.rs @@ -0,0 +1,359 @@ +//! `python::demos_qwen` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +/// **v0.8.0 — análise/prova/teste das 4 propostas Qwen3.8** (r-33): mede +/// custo e exatidão de (1) Newton analítico vs golden-section (verificador), +/// (2) Cayley vs polar (FNH), (3) árvore pós-ordem iterativa vs recursiva, +/// (4) MoE vetorizada vs referência (valores + gradiente restaurado — bug +/// v080-1), e re-executa a ablação JEPA de 3 braços com a correção. +#[pyfunction] +#[allow(deprecated)] // mede o bug v080-1 intencionalmente (fwd = oráculo) +pub(crate) fn demo_qwen_v080(py: Python) -> PyResult { + use candle_core::{Device, Tensor}; + use std::time::Instant; + let dev = Device::Cpu; + + // ---------- (1) Newton vs golden-section (verificador) ---------- + let mut rng = crate::rng::RngDeterministico::novo(808); + let (n, k) = (1024usize, 32usize); + let mut lv = Vec::with_capacity(n * k); + let mut av = Vec::with_capacity(n); + for _ in 0..n { + let alvo = rng.inteiro(k) as u32; + av.push(alvo); + for j in 0..k { + let base = if j as u32 == alvo { 3.0 } else { 0.0 }; + lv.push(base + rng.normal(0.0, 1.0) as f32); + } + } + let lt = Tensor::from_vec(lv, (n, k), &dev).unwrap(); + let at = Tensor::from_vec(av, (n,), &dev).unwrap(); + let t0 = Instant::now(); + let (t_g, _, nll_g) = verificador::calibrar_temperatura(<, &at).unwrap(); + let tempo_golden = t0.elapsed().as_secs_f64(); + let t0 = Instant::now(); + let (t_n, _, nll_n, n_aval) = + verificador::calibrar_temperatura_newton(<, &at, 40).unwrap(); + let tempo_newton = t0.elapsed().as_secs_f64(); + + // ---------- (2) Cayley vs polar (FNH) ---------- + let n_rot = 16usize; + let r = fnh::RotacaoSkew::nova(n_rot, &dev).unwrap(); + let th: Vec = (0..n_rot * (n_rot - 1) / 2) + .map(|i| ((i as f32) * 0.11) % 0.5) + .collect(); + r.theta + .set(&Tensor::from_vec(th, (n_rot * (n_rot - 1) / 2,), &dev).unwrap()) + .unwrap(); + let rep = 25usize; + let t0 = Instant::now(); + let mut delta_polar = 0.0; + for _ in 0..rep { + delta_polar = crate::stiefel::erro_ortogonalidade(&r.rotacao_polar().unwrap()).unwrap(); + } + let tempo_polar = t0.elapsed().as_secs_f64() / rep as f64; + let t0 = Instant::now(); + let mut delta_cayley = 0.0; + for _ in 0..rep { + delta_cayley = crate::stiefel::erro_ortogonalidade(&r.rotacao_cayley().unwrap()).unwrap(); + } + let tempo_cayley = t0.elapsed().as_secs_f64() / rep as f64; + + // ---------- (3) árvore iterativa vs recursiva ---------- + let mut arv = arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new()); + let mut pai = 1usize; + for i in 0..20_000u32 { + pai = arv.anexar_filho(pai, &format!("c{i}"), 1); + } + arv.nos[pai].metricas.insert("m".into(), 1.0); + let t0 = Instant::now(); + let total_it = arv.agregar_fundo_topo("m"); + let tempo_arv_it = t0.elapsed().as_secs_f64(); + // repõe TODAS as métricas locais (a agregação sobrescreveu com os + // agregados) — só a folha tem valor; o resto volta a 0 — e roda a + // recursiva (profundidade 20k ok na thread principal do Python) + for no in arv.nos.iter_mut() { + no.metricas.insert("m".into(), 0.0); + } + arv.nos[pai].metricas.insert("m".into(), 1.0); + let t0 = Instant::now(); + let total_rec = arv.agregar_fundo_topo_rec("m"); + let tempo_arv_rec = t0.elapsed().as_secs_f64(); + + // ---------- (4) MoE vetorizada: valores + gradiente + custo ---------- + let (nm, dm, em) = (256usize, 32usize, 8usize); + let xv: Vec = (0..nm * dm).map(|_| rng.normal(0.0, 1.0) as f32).collect(); + let x = Tensor::from_vec(xv, (nm, dm), &dev).unwrap(); + let moe = moe_redirecionavel::MoeRedirecionavel::novo(dm, em, 2, nm, &dev).unwrap(); + let s_ref = moe.fwd(&x, moe_redirecionavel::Papel::Codificador).unwrap(); + let s_vet = moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap(); + let dif_moe = (&s_ref.saida - &s_vet.saida) + .unwrap() + .abs() + .unwrap() + .max_all() + .unwrap() + .to_scalar::() + .unwrap() as f64; + let rep_moe = 10usize; + let t0 = Instant::now(); + for _ in 0..rep_moe { + let _ = moe.fwd(&x, moe_redirecionavel::Papel::Codificador).unwrap(); + } + let tempo_moe_ref = t0.elapsed().as_secs_f64() / rep_moe as f64; + let t0 = Instant::now(); + for _ in 0..rep_moe { + let _ = moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap(); + } + let tempo_moe_vet = t0.elapsed().as_secs_f64() / rep_moe as f64; + // normas de gradiente (bug v080-1: fwd = 0; vetorizada > 0) + let norma = |vet: bool| -> (f64, f64) { + let s = if vet { + moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap() + } else { + moe.fwd(&x, moe_redirecionavel::Papel::Codificador).unwrap() + }; + let perda = s.saida.sqr().unwrap().sum_all().unwrap(); + let gs = perda.backward().unwrap(); + let mut nr = 0.0; + for v in moe.roteador.vars() { + if let Some(g) = gs.get(&v) { + nr += g.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64; + } + } + let mut ne = 0.0; + for e in &moe.especialistas { + for v in e.vars() { + if let Some(g) = gs.get(&v) { + ne += g.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64; + } + } + } + (nr, ne) + }; + let (grad_ref_r, grad_ref_e) = norma(false); + let (grad_vet_r, grad_vet_e) = norma(true); + + // ---------- (5) re-ablação JEPA 3 braços (gradiente restaurado) ---------- + let xv: Vec = (0..64 * 12).map(|_| rng.normal(0.0, 1.0) as f32).collect(); + let xj = Tensor::from_vec(xv, (64, 12), &dev).unwrap(); + let mut ablac = serde_json::Map::new(); + for modo in ["pequena", "densa", "moe"] { + let rj = moe_redirecionavel::treinar_jepa_com_moe(&xj, modo, 120, 0.5, 42).unwrap(); + ablac.insert( + modo.into(), + serde_json::json!({ + "perda_inicial": rj.perda_pred_inicial, + "perda_final": rj.perda_pred_final, + "queda_pct": 100.0 * (1.0 - rj.perda_pred_final / rj.perda_pred_inicial.max(1e-9)), + }), + ); + } + + let d_ = serde_json::json!({ + "versao": "qwen-v080-1.0", + "newton_vs_golden": { + "t_golden": t_g, "t_newton": t_n, "dif_t": (t_g - t_n).abs(), + "nll_golden": nll_g, "nll_newton": nll_n, + "nll_dif": (nll_g - nll_n).abs(), + "n_aval_newton": n_aval, + "tempo_golden_s": tempo_golden, "tempo_newton_s": tempo_newton, + "speedup": tempo_golden / tempo_newton.max(1e-9), + "gates": { + "mesmo_t": (t_g - t_n).abs() < 1e-3, + "nll_nao_piora": nll_n <= nll_g + 1e-4, + "menos_avaliacoes": n_aval < 36, + }, + }, + "cayley_vs_polar": { + "delta_ort_polar": delta_polar, "delta_ort_cayley": delta_cayley, + "tempo_polar_s": tempo_polar, "tempo_cayley_s": tempo_cayley, + "speedup": tempo_polar / tempo_cayley.max(1e-9), + "gates": { + "ambos_exatos": delta_polar < 1e-6 && delta_cayley < 1e-6, + "cayley_mais_rapido": tempo_cayley < tempo_polar, + }, + }, + "arvore_iter_vs_rec": { + "n_nos": arv.n_nos(), + "total_iter": total_it, "total_rec": total_rec, + "bit_a_bit": total_it.to_bits() == total_rec.to_bits(), + "tempo_iter_s": tempo_arv_it, "tempo_rec_s": tempo_arv_rec, + "gates": { "iguais_bit_a_bit": total_it == total_rec }, + }, + "moe_vetorizada": { + "max_dif_valores": dif_moe, + "tempo_ref_s": tempo_moe_ref, "tempo_vet_s": tempo_moe_vet, + "grad_roteador_ref": grad_ref_r, "grad_especialistas_ref": grad_ref_e, + "grad_roteador_vet": grad_vet_r, "grad_especialistas_vet": grad_vet_e, + "gates": { + "valores_iguais": dif_moe < 1e-4, + "bug_v080_1_confirmado": grad_ref_r == 0.0 && grad_ref_e == 0.0, + "gradiente_restaurado": grad_vet_r > 0.0 && grad_vet_e > 0.0, + }, + }, + "ablacao_jepa_3_bracos_v080": ablac, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) +} + + +/// **v0.9.0 — veredito executável das críticas Qwen3.8 (r-34):** mede os +/// aprimoramentos contra as referências v0.8.0 (oráculos preservados): +/// (1) skew O(n²) via index_add — bit-a-bit + tempo + memória analítica; +/// (2) calibrar vetorial — bit-a-bit em 3 passos EMA + tempo; +/// (3) forward canônico (ex-fwd_vetorizado) — regressão de valores e +/// gradiente; +/// (4) newtypes de domínio — rejeição de fora-de-domínio. +#[pyfunction] +pub(crate) fn demo_criticas_v090(py: Python) -> PyResult { + use candle_core::{Device, Tensor}; + use std::time::Instant; + let dev = Device::Cpu; + let mut rng = crate::rng::RngDeterministico::novo(909); + + // ---------- (1) skew: index_add O(n²) vs matmul de seleção O(n⁴) ---------- + let mut skew_rel = serde_json::Map::new(); + let mut bits_todos = true; + for &n in &[16usize, 64, 128] { + let r = fnh::RotacaoSkew::nova(n, &dev).unwrap(); + let m = n * (n - 1) / 2; + let th: Vec = (0..m).map(|_| rng.uniforme_intervalo(-0.9, 0.9) as f32).collect(); + r.theta.set(&Tensor::from_vec(th, (m,), &dev).unwrap()).unwrap(); + // bit-a-bit (F-13) + let an = r.skew().unwrap().flatten_all().unwrap().to_vec1::().unwrap(); + let ar = r.skew_matmul_referencia().unwrap().flatten_all().unwrap().to_vec1::().unwrap(); + let bits_iguais = an.iter().zip(ar.iter()).all(|(a, b)| a.to_bits() == b.to_bits()); + bits_todos &= bits_iguais; + // tempo (referência esquentada 1×; média de 5) + let _ = r.skew_matmul_referencia().unwrap(); + let t0 = Instant::now(); + for _ in 0..5 { let _ = r.skew_matmul_referencia().unwrap(); } + let t_ref = t0.elapsed().as_secs_f64() / 5.0; + let _ = r.skew().unwrap(); + let t0 = Instant::now(); + for _ in 0..5 { let _ = r.skew().unwrap(); } + let t_novo = t0.elapsed().as_secs_f64() / 5.0; + let bytes_ref = (m * n * n * 4) as f64; + let bytes_novo = ((n * n + 3 * m) * 4 + 2 * m * 4) as f64; + skew_rel.insert( + format!("n_{n}"), + serde_json::json!({ + "tempo_ref_s": t_ref, "tempo_novo_s": t_novo, + "speedup": t_ref / t_novo.max(1e-12), + "bytes_ref_selecao": bytes_ref, "bytes_novo": bytes_novo, + "razao_memoria": bytes_ref / bytes_novo, + "bits_iguais": bits_iguais, + }), + ); + } + + // ---------- (2) calibrar: tensor vs laços nativos ---------- + let (d_out, d_in, n_lin) = (512usize, 512usize, 4096usize); + let wv: Vec = (0..d_out * d_in).map(|_| rng.uniforme_intervalo(-2.0, 2.0) as f32).collect(); + let w = Tensor::from_vec(wv, (d_out, d_in), &dev).unwrap(); + let xv: Vec = (0..n_lin * d_in).map(|_| rng.uniforme_intervalo(-3.0, 3.0) as f32).collect(); + let x = Tensor::from_vec(xv, (n_lin, d_in), &dev).unwrap(); + let mut s_novo = fnh::SuavizadorQuant::novo(0.5, 0.98); + let mut s_tens = fnh::SuavizadorQuant::novo(0.5, 0.98); + let mut s_ref = fnh::SuavizadorQuant::novo(0.5, 0.98); + let mut bits_cal = true; + for _ in 0..3 { + s_novo.calibrar(&w, &x).unwrap(); + s_tens.calibrar_tensor(&w, &x).unwrap(); + s_ref.calibrar_referencia(&w, &x).unwrap(); + for (a, b) in s_novo.w_max.as_ref().unwrap().iter().zip(s_ref.w_max.as_ref().unwrap()) { + bits_cal &= a.to_bits() == b.to_bits(); + } + for (a, b) in s_novo.x_max.as_ref().unwrap().iter().zip(s_ref.x_max.as_ref().unwrap()) { + bits_cal &= a.to_bits() == b.to_bits(); + } + } + // tempo (esquentadas 1×; média de 5) + let _ = s_ref.calibrar_referencia(&w, &x); + let t0 = Instant::now(); + for _ in 0..5 { s_ref.calibrar_referencia(&w, &x).unwrap(); } + let t_cal_ref = t0.elapsed().as_secs_f64() / 5.0; + let _ = s_novo.calibrar(&w, &x); + let t0 = Instant::now(); + for _ in 0..5 { s_novo.calibrar(&w, &x).unwrap(); } + let t_cal_novo = t0.elapsed().as_secs_f64() / 5.0; + let _ = s_tens.calibrar_tensor(&w, &x); + let t0 = Instant::now(); + for _ in 0..5 { s_tens.calibrar_tensor(&w, &x).unwrap(); } + let t_cal_tens = t0.elapsed().as_secs_f64() / 5.0; + + // ---------- (3) forward: regressão de valores + gradiente ---------- + let (nm, dm, em) = (256usize, 32usize, 8usize); + let xv2: Vec = (0..nm * dm).map(|_| rng.normal(0.0, 1.0) as f32).collect(); + let x2 = Tensor::from_vec(xv2, (nm, dm), &dev).unwrap(); + #[allow(deprecated)] // oráculo do bug v080-1 (r-33 §5) + let moe = moe_redirecionavel::MoeRedirecionavel::novo(dm, em, 2, nm, &dev).unwrap(); + #[allow(deprecated)] + let s_refm = moe.fwd(&x2, moe_redirecionavel::Papel::Codificador).unwrap(); + let s_fwd = moe.forward(&x2, moe_redirecionavel::Papel::Codificador).unwrap(); + let dif_moe = (&s_refm.saida - &s_fwd.saida).unwrap().abs().unwrap() + .max_all().unwrap().to_scalar::().unwrap() as f64; + let perda = s_fwd.saida.sqr().unwrap().sum_all().unwrap(); + let gs = perda.backward().unwrap(); + let mut gr = 0.0f64; + for v in moe.roteador.vars() { + if let Some(g) = gs.get(&v) { + gr += g.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64; + } + } + let mut ge = 0.0f64; + for e in &moe.especialistas { + for v in e.vars() { + if let Some(g) = gs.get(&v) { + ge += g.sqr().unwrap().sum_all().unwrap().to_scalar::().unwrap() as f64; + } + } + } + + // ---------- (4) newtypes de domínio (T-V1) ---------- + let rejeita = crate::tipos::Probabilidade::novo(7.0).is_err() + && crate::tipos::Probabilidade::novo(-0.5).is_err() + && crate::tipos::Probabilidade::novo(f64::NAN).is_err() + && crate::tipos::GateAtivacao::novo(1.5).is_err(); + let aceita = crate::tipos::Probabilidade::novo(0.37).unwrap().valor() == 0.37; + let gate_tipado = fnh::GatePunicao::default() + .valor_tipado(&crate::tipos::Probabilidade::novo(0.9).unwrap()) + .valor(); + + let d_ = serde_json::json!({ + "versao": "criticas-v090-1.0", + "skew_index_add": { + "por_n": skew_rel, + "gates": { + "bits_iguais_todos_n": bits_todos, + }, + }, + "calibrar_zero_copia": { + "bits_iguais_3_passos": bits_cal, + "tempo_ref_s": t_cal_ref, "tempo_novo_s": t_cal_novo, + "tempo_tensor_s": t_cal_tens, + "speedup_zero_copia": t_cal_ref / t_cal_novo.max(1e-12), + "razao_tensor_vs_zero_copia": t_cal_tens / t_cal_novo.max(1e-12), + "gates": { "bit_a_bit": bits_cal, "nao_regride": t_cal_novo <= t_cal_ref * 1.05 }, + }, + "forward_canonico": { + "max_dif_valores_vs_oraculo": dif_moe, + "grad_roteador": gr, "grad_especialistas": ge, + "gates": { + "valores_iguais": dif_moe < 1e-4, + "gradiente_ativo": gr > 0.0 && ge > 0.0, + }, + }, + "newtypes_dominio": { + "rejeita_fora_do_dominio": rejeita, + "aceita_dentro": aceita, + "gate_tipado_em_0_9": gate_tipado, + "gates": { "fronteira_validada": rejeita && aceita }, + }, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) +} diff --git a/src/python/fnh_arvore.rs b/src/python/fnh_arvore.rs new file mode 100644 index 0000000000000000000000000000000000000000..b7de1821592354571a358b470f66595302a5c8f9 --- /dev/null +++ b/src/python/fnh_arvore.rs @@ -0,0 +1,344 @@ +//! `python::fnh_arvore` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + +use super::nucleo::ConfigPy; +use super::treino_fases::{carregar_gerador, carregar_tokenizador}; + + + +// --------------------------------------------------------------------------- +// v0.7.0 — bindings dos módulos novos (FNH, árvore, quebra-cabeça, NLP/NLG) +// --------------------------------------------------------------------------- + +/// Árvore de pesos indexada (arestas duplas) exposta ao Python. +#[pyclass(name = "ArvorePesos")] +pub(crate) struct ArvorePesosPy { + arvore: arvore_pesos::ArvorePesos, +} + +#[pymethods] +impl ArvorePesosPy { + #[staticmethod] + #[pyo3(signature = (contagens=None))] + fn nova(contagens: Option>) -> Self { + let c = contagens.unwrap_or_default(); + ArvorePesosPy { arvore: arvore_pesos::ArvorePesos::nova(&c) } + } + + fn invariantes_ok(&self) -> bool { + self.arvore.invariantes_ok().is_ok() + } + + fn agregar(&mut self, metrica: &str) -> f64 { + self.arvore.agregar_fundo_topo(metrica) + } + + fn aplicar_ajuste( + &mut self, + no: &str, + metrica: &str, + delta: f64, + delta_max: f64, + ) -> PyResult { + let idx = self + .arvore + .no(no) + .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(format!("nó '{no}' inexistente")))?; + Ok(self.arvore.aplicar_ajuste_indexado(idx, metrica, delta, delta_max)) + } + + fn definir_metrica(&mut self, no: &str, metrica: &str, valor: f64) -> PyResult<()> { + let idx = self + .arvore + .no(no) + .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(format!("nó '{no}' inexistente")))?; + self.arvore.nos[idx].metricas.insert(metrica.to_string(), valor); + Ok(()) + } + + fn metrica(&self, no: &str, metrica: &str) -> PyResult { + let idx = self + .arvore + .no(no) + .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(format!("nó '{no}' inexistente")))?; + Ok(self.arvore.nos[idx].metricas.get(metrica).copied().unwrap_or(0.0)) + } + + fn regular(&mut self, origem: &str, delta: f64, lambda: f64) -> PyResult<()> { + let idx = self + .arvore + .no(origem) + .ok_or_else(|| pyo3::exceptions::PyKeyError::new_err(format!("nó '{origem}' inexistente")))?; + self.arvore.regular_topo_fundo(idx, delta, lambda); + Ok(()) + } + + fn dump(&self) -> PyResult { + Python::with_gil(|py| { + let nos: Vec = self + .arvore + .nos + .iter() + .map(|no| { + serde_json::json!({ + "nome": no.nome, "ini": no.ini, "fim": no.fim, + "profundidade": no.profundidade, + "metricas": no.metricas, + }) + }) + .collect(); + let d = serde_json::json!({ + "n_nos": self.arvore.n_nos(), + "n_params": self.arvore.n_params, + "invariantes_ok": self.arvore.invariantes_ok().is_ok(), + "nos": nos, + }); + Ok(pythonize::pythonize(py, &d)?.into()) + }) + } +} + + +/// Demo/gate da camada FNH sobre o backbone: mede o efeito do gate de +/// punição nas rotações aprendíveis (ablação: π baixa vs π alta). +#[pyfunction] +#[pyo3(signature = (d_in=8, d_out=6, passos=60, seed=42))] +pub(crate) fn demo_fnh(py: Python, d_in: usize, d_out: usize, passos: usize, seed: u64) -> PyResult { + let dev = candle_core::Device::Cpu; + let mut rng = rng::RngDeterministico::novo(seed); + let mut camada = fnh::CamadaFNH::nova(d_in, d_out, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fnh: {e}")))?; + let x: Vec = (0..4 * d_in).map(|_| rng.normal_padrao() as f32).collect(); + let xt = candle_core::Tensor::from_vec(x, (4, d_in), &dev) + .map_err(py_err)?; + camada + .suavizador + .calibrar(camada.base.w.as_tensor(), &xt) + .map_err(py_err)?; + // treino curto com punição ALTA (gate abre) vs BAIXA (gate fechado) + let mut resumo = serde_json::Map::new(); + for (rotulo, punicao) in [("baixa", 0.1f64), ("alta", 0.95)] { + let mut cam = fnh::CamadaFNH::nova(d_in, d_out, &dev).map_err(py_err)?; + // mesma semente de pesos: copia os valores + let _ = &mut cam; + let mut hub = telemetria::TelemetryHub::novo(None); + let mut perdas = Vec::new(); + for p in 0..passos { + // ruído no x a cada passo (regime não-estacionário leve) + let ruido: Vec = (0..4 * d_in) + .map(|_| (rng.normal(0.0, 0.05)) as f32) + .collect(); + let xt2 = (&xt + + &candle_core::Tensor::from_vec(ruido, (4, d_in), &dev).map_err(py_err)?) + .map_err(py_err)?; + let y = cam.forward(&xt2, punicao).map_err(py_err)?; + // alvo: saída da camada base (auto-referência: regular para W) + let z_ref = cam.linear_sem_bias(&xt2, cam.base.w.as_tensor()).map_err(py_err)?; + let perda = (&y).sub(&z_ref).map_err(py_err)?.sqr().map_err(py_err)?.sum_all().map_err(py_err)?; + perdas.push(perda.to_scalar::().map_err(py_err)? as f64); + let grads = perda.backward().map_err(py_err)?; + // gradiente descendente manual simples nos θ das rotações + if let Some(g) = grads.get(&cam.r_wp.theta) { + let gv = g.flatten_all().map_err(py_err)?.to_vec1::().map_err(py_err)?; + let th = cam.r_wp.theta.as_tensor().flatten_all().map_err(py_err)?.to_vec1::().map_err(py_err)?; + let novo: Vec = th.iter().zip(gv.iter()).map(|(t, g_)| t - 0.05 * g_).collect(); + cam.r_wp + .theta + .set(&candle_core::Tensor::from_vec(novo, th.len(), &dev).map_err(py_err)?) + .map_err(py_err)?; + } + hub.registrar("fnh_gate", cam.gate_ultimo); + hub.registrar("fnh_punicao", punicao); + hub.registrar("perda", perdas[p]); + let _ = p; + } + let theta_norma: f64 = cam + .r_wp + .theta + .as_tensor() + .flatten_all() + .map_err(py_err)? + .to_vec1::() + .map_err(py_err)? + .iter() + .map(|v| (*v as f64).powi(2)) + .sum::() + .sqrt(); + let delta_ort = cam.r_wp.erro_ortogonalidade().map_err(py_err)?; + resumo.insert( + format!("punicao_{rotulo}"), + serde_json::json!({ + "gate_final": cam.gate_ultimo, + "theta_norma": theta_norma, + "delta_ort": delta_ort, + "perda_inicial": perdas[0], + "perda_final": perdas[perdas.len() - 1], + }), + ); + } + let _ = &mut camada; + let d = serde_json::json!({ + "versao": "fnh-0.7.0", + "resumo": resumo, + }); + Ok(pythonize::pythonize(py, &d)?.into()) +} + + +/// **Quebra-cabeça sobre o conhecimento TREINADO REAL** (script 63): +/// carrega o tokenizador (codebook do nível `nivel` — o conhecimento +/// aprendido dos dados reais) e o gerador (a cabeça real como logits_fn +/// do verificador); executa o protocolo completo de r-30 Q-AD. +#[pyfunction] +#[pyo3(signature = (cfg, caminho_tokenizador, caminho_gerador, n_pecas=4, n_rodadas=12, tau_c=0.4, nivel=0, seed=777))] +pub(crate) fn quebra_cabeca_real( + cfg: &ConfigPy, + caminho_tokenizador: &str, + caminho_gerador: &str, + n_pecas: usize, + n_rodadas: usize, + tau_c: f64, + nivel: usize, + seed: u64, +) -> PyResult { + Python::with_gil(|py| { + let dev = candle_core::Device::Cpu; + crate::rng::definir_semente(seed); + let mut rng = rng::RngDeterministico::novo(seed); + let mut tok = tokenizador::TokenizadorConceitos::novo(&cfg.cfg, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("tok: {e}")))?; + carregar_tokenizador(&mut tok, caminho_tokenizador) + .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; + let mut ger = gerador::GeradorConceitos::novo(&cfg.cfg, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("ger: {e}")))?; + ger.carregar(caminho_gerador) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("ger carregar: {e}")))?; + let nivel = nivel.min(tok.quants.len().saturating_sub(1)); + // conhecimento treinado REAL: codebook Stiefel (K, d_lat) + let codebook = tok + .quants[nivel] + .codebook() + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("codebook: {e}")))?; + let d_lat = codebook.dims()[1]; + let k = ger.k; + // δ_ort REAL do codebook treinado (rotação Stiefel em treino — A2) + let delta_ort_codebook = tok.quants[nivel] + .delta_ort() + .unwrap_or(1.0); + // logits_fn REAL: agregado (d_lat,) → proj_entrada → norm → cabeça + let proj = &ger.proj_entrada; + let norm = &ger.norm_entrada; + let cabeca = &ger.cabeca.cabecas[nivel]; + let logits_fn = move |x: &candle_core::Tensor| -> candle_core::Result { + let h = proj.fwd(&x.reshape((1, d_lat))?)?; + let h = norm.fwd(&h)?; + let lg = cabeca.fwd(&h)?; + lg.reshape((k,)) + }; + // verificador com temperatura calibrada pela fase C quando + // disponível; default 1,0 (a cota V-2 aplica-se com ECE medido) + let verif = verificador::VerificadorNcm::novo(); + let mut arvore = arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new()); + let cfg_qc = pensamento::ConfigQuebraCabeca::novo(n_pecas, n_rodadas, tau_c); + let r = pensamento::quebra_cabeca_inovar(&codebook, &verif, &logits_fn, &mut arvore, &cfg_qc, &mut rng) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("qc: {e}")))?; + let rodadas: Vec = r + .rodadas + .iter() + .map(|x| { + serde_json::json!({ + "rodada": x.rodada, "confianca": x.confianca, + "aceito": x.aceito, "delta": x.delta_aplicado, + }) + }) + .collect(); + let d_ = serde_json::json!({ + "versao": "quebra-cabeca-real-0.7.0", + "nivel": nivel, + "delta_ort_codebook": delta_ort_codebook, + "n_conceitos": codebook.dims()[0], + "d_lat": d_lat, + "aceitos": r.aceitos, + "rejeitados": r.rejeitados, + "confianca_base": r.confianca_base, + "confianca_melhor": r.confianca_melhor, + "telemetria": r.telemetria, + "rodadas": rodadas, + "arvore_invariantes": arvore.invariantes_ok().is_ok(), + "punicao_no_pensamento": arvore + .no("pensamento") + .and_then(|i| arvore.nos[i].metricas.get("punicao").copied()) + .unwrap_or(0.0), + }); + Ok(pythonize::pythonize(py, &d_)?.into()) + }) +} + + +/// Demo/gate do quebra-cabeça sobre conceitos SINTÉTICOS estruturados +/// (validação de mecanismo; a avaliação com dados reais vive no script 63). +#[pyfunction] +#[pyo3(signature = (m=24, d=8, n_pecas=4, n_rodadas=12, tau_c=0.35, seed=777))] +pub(crate) fn demo_quebra_cabeca( + py: Python, + m: usize, + d: usize, + n_pecas: usize, + n_rodadas: usize, + tau_c: f64, + seed: u64, +) -> PyResult { + let dev = candle_core::Device::Cpu; + let mut rng = rng::RngDeterministico::novo(seed); + // conceitos estruturados: cluster dominante +1 (média direcional) com + // 25% de outliers fracos — o agregado TEM direção (sem cancelamento) + let mut v = Vec::with_capacity(m * d); + for i in 0..m { + let centro = if i < m / 4 { 0.2 } else { 1.0 }; + for j in 0..d { + v.push((centro + rng.normal(0.0, 0.25)) as f32); + } + } + let conceitos = candle_core::Tensor::from_vec(v, (m, d), &dev).map_err(py_err)?; + // logits_fn ASSIMÉTRICO: projeção fixa W_proj (rotação-sensível — a + // agregação simétrica seria invariante sob permutação/rotação, e o + // backbone real é exatamente o avaliador NÃO-invariente da prática). + let mut wv = Vec::with_capacity(d * 4); + for _ in 0..d * 4 { + wv.push((rng.normal_padrao() as f32) * 2.5); + } + let w_proj = candle_core::Tensor::from_vec(wv, (d, 4), &dev).map_err(py_err)?; + // verificador com temperatura padrão + let verif = verificador::VerificadorNcm::novo(); + let logits_fn = |x: &candle_core::Tensor| -> candle_core::Result { + // logits = xᵀ·W_proj (4,) — sensível à direção do agregado + x.reshape((1, d))?.matmul(&w_proj)?.reshape((4,)) + }; + let mut arvore = arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new()); + let cfg = pensamento::ConfigQuebraCabeca::novo(n_pecas, n_rodadas, tau_c); + let r = pensamento::quebra_cabeca_inovar(&conceitos, &verif, &logits_fn, &mut arvore, &cfg, &mut rng) + .map_err(py_err)?; + let rodadas: Vec = r + .rodadas + .iter() + .map(|x| { + serde_json::json!({ + "rodada": x.rodada, "confianca": x.confianca, + "aceito": x.aceito, "delta": x.delta_aplicado, + }) + }) + .collect(); + let d_ = serde_json::json!({ + "versao": "quebra-cabeca-0.7.0", + "aceitos": r.aceitos, + "rejeitados": r.rejeitados, + "confianca_base": r.confianca_base, + "confianca_melhor": r.confianca_melhor, + "telemetria": r.telemetria, + "rodadas": rodadas, + "arvore_invariantes": arvore.invariantes_ok().is_ok(), + }); + Ok(pythonize::pythonize(py, &d_)?.into()) +} diff --git a/src/python/jepa_treinos.rs b/src/python/jepa_treinos.rs new file mode 100644 index 0000000000000000000000000000000000000000..3dce01a8e5f72c6e34382f36b0ea2cb5166a5587 --- /dev/null +++ b/src/python/jepa_treinos.rs @@ -0,0 +1,251 @@ +//! `python::jepa_treinos` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +/// Treina uma variante JEPA (7 variantes por máscara; lejepa/mcjepa/bjepa +/// têm funções próprias) sobre patches (n, d) com grade dada. +#[pyfunction] +pub(crate) fn treinar_jepa(py: Python, dados: Vec>, grid: Vec, variante: String, cfg: Option) -> PyResult { + let dev = candle_core::Device::Cpu; + let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut c = jepa::CfgJepa::default(); + if let Some(js) = &cfg { + if let Ok(v) = serde_json::from_str::(js) { + if let Some(o) = v.as_object() { + for (k, val) in o { + match k.as_str() { + "d_emb" => c.d_emb = val.as_u64().unwrap_or(32) as usize, + "epocas" => c.epocas = val.as_u64().unwrap_or(60) as usize, + "lr" => c.lr = val.as_f64().unwrap_or(3e-3), + "m" => c.m = val.as_f64().unwrap_or(0.99), + "lambda_vc" => c.lambda_vc = val.as_f64().unwrap_or(0.5), + "gamma_vc" => c.gamma_vc = val.as_f64().unwrap_or(1.0), + "frac_mascara" => c.frac_mascara = val.as_f64().unwrap_or(0.35), + "semente" => c.semente = val.as_u64().unwrap_or(42), + _ => {} + } + } + } + } + } + let var = match variante.as_str() { + "image" => jepa::VarianteJepa::ImageJepa, + "vjepa2" => jepa::VarianteJepa::VJepa2, + "audio" => jepa::VarianteJepa::AudioJepa, + "point" => jepa::VarianteJepa::PointJepa, + "jepa3d" => jepa::VarianteJepa::Jepa3D, + "brain" => jepa::VarianteJepa::BrainJepa, + "tjepa" => jepa::VarianteJepa::TJepa, + _ => jepa::VarianteJepa::TJepa, + }; + let grid2 = if grid.is_empty() { vec![t.dim(0).unwrap_or(1)] } else { grid }; + let rel = jepa::treinar_variante( + &t, + &|_n, r| jepa::mascara_por_variante(var, &grid2, c.frac_mascara, r), + &c, + var, + None, + &dev, + ) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let d = serde_json::json!({ + "versao": jepa::versao(), + "variante": rel.variante, + "perda_inicial": rel.perda_inicial, + "perda_final": rel.perda_final, + "perda_pred_inicial": rel.perda_pred_inicial, + "perda_pred_final": rel.perda_pred_final, + "queda_pct": if rel.perda_pred_inicial.is_finite() && rel.perda_pred_inicial > 0.0 { + 100.0 * (1.0 - rel.perda_pred_final / rel.perda_pred_inicial) + } else { 0.0 }, + "std_emb_inicial": rel.std_emb_inicial, + "std_emb_final": rel.std_emb_final, + "colapso_evitado": rel.std_emb_final > 0.05, + "n_patches": rel.n_patches, + "epocas": rel.epocas, + }); + Ok(pythonize::pythonize(py, &d)?.into()) +} + + +/// Treina LeJEPA (edição-condicionado): dados + instruções + editados. +#[pyfunction] +pub(crate) fn treinar_lejepa(py: Python, dados: Vec>, edits: Vec>, editados: Vec>, cfg: Option) -> PyResult { + let dev = candle_core::Device::Cpu; + let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let e = tensor_de_2d(edits, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let y = tensor_de_2d(editados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut c = jepa::CfgJepa::default(); + if let Some(js) = &cfg { + if let Ok(v) = serde_json::from_str::(js) { + if let Some(o) = v.as_object() { + if let Some(x) = o.get("epocas") { c.epocas = x.as_u64().unwrap_or(60) as usize; } + if let Some(x) = o.get("d_emb") { c.d_emb = x.as_u64().unwrap_or(32) as usize; } + if let Some(x) = o.get("lr") { c.lr = x.as_f64().unwrap_or(3e-3); } + if let Some(x) = o.get("semente") { c.semente = x.as_u64().unwrap_or(42); } + } + } + } + let rel = jepa::treinar_lejepa(&t, &e, &y, &c, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let d = serde_json::json!({ + "variante": rel.variante, "perda_inicial": rel.perda_inicial, + "perda_final": rel.perda_final, "std_emb_final": rel.std_emb_final, + "colapso_evitado": rel.std_emb_final > 0.05, + }); + Ok(pythonize::pythonize(py, &d)?.into()) +} + + +/// Treina MC-JEPA (cruz-modal): duas modalidades pareadas. +#[pyfunction] +pub(crate) fn treinar_mcjepa(py: Python, mod_a: Vec>, mod_b: Vec>, cfg: Option) -> PyResult { + let dev = candle_core::Device::Cpu; + let a = tensor_de_2d(mod_a, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let b = tensor_de_2d(mod_b, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut c = jepa::CfgJepa::default(); + if let Some(js) = &cfg { + if let Ok(v) = serde_json::from_str::(js) { + if let Some(o) = v.as_object() { + if let Some(x) = o.get("epocas") { c.epocas = x.as_u64().unwrap_or(60) as usize; } + if let Some(x) = o.get("d_emb") { c.d_emb = x.as_u64().unwrap_or(32) as usize; } + if let Some(x) = o.get("lr") { c.lr = x.as_f64().unwrap_or(3e-3); } + } + } + } + let rel = jepa::treinar_mcjepa(&a, &b, &c, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let d = serde_json::json!({ + "variante": rel.variante, "perda_inicial": rel.perda_inicial, + "perda_final": rel.perda_final, "std_emb_final": rel.std_emb_final, + "colapso_evitado": rel.std_emb_final > 0.05, + }); + Ok(pythonize::pythonize(py, &d)?.into()) +} + + +/// Treina BJEPA (bidirecional com consistência de ciclo) sobre sequência. +#[pyfunction] +pub(crate) fn treinar_bjepa(py: Python, serie: Vec>, cfg: Option) -> PyResult { + let dev = candle_core::Device::Cpu; + let t = tensor_de_2d(serie, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut c = jepa::CfgJepa::default(); + if let Some(js) = &cfg { + if let Ok(v) = serde_json::from_str::(js) { + if let Some(o) = v.as_object() { + if let Some(x) = o.get("epocas") { c.epocas = x.as_u64().unwrap_or(60) as usize; } + if let Some(x) = o.get("d_emb") { c.d_emb = x.as_u64().unwrap_or(32) as usize; } + if let Some(x) = o.get("lr") { c.lr = x.as_f64().unwrap_or(3e-3); } + } + } + } + let rel = jepa::treinar_bjepa(&t, &c, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let d = serde_json::json!({ + "variante": rel.variante, "perda_inicial": rel.perda_inicial, + "perda_final": rel.perda_final, "std_emb_final": rel.std_emb_final, + "colapso_evitado": rel.std_emb_final > 0.05, + }); + Ok(pythonize::pythonize(py, &d)?.into()) +} + + +/// Demo SonicMoE: roteamento top-k com capacidade e perda de balanceamento. +#[pyfunction] +pub(crate) fn demo_sonic_moe(py: Python, n: usize, d: usize, n_expertos: usize, top_k: usize, capacidade: usize) -> PyResult { + let dev = candle_core::Device::Cpu; + let moe = jepa::SonicMoe::novo(d, n_expertos, top_k, capacidade, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let x = candle_core::Tensor::randn(0f32, 1.0, (n, d), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let s = moe.fwd(&x).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let aux = s.perda_aux.to_scalar::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? as f64; + let d_ = serde_json::json!({ + "versao": jepa::versao(), + "carga_por_especialista": s.carga, + "descartados": s.descartados, + "perda_aux": aux, + "capacidade_respeitada": s.carga.iter().all(|&c| c <= capacidade), + "saida_forma": s.saida.dims().to_vec(), + }); + Ok(pythonize::pythonize(py, &d_)?.into()) +} + + +/// Demo ReplaySSM: estabilidade + replay em cenário de troca de tarefa. +#[pyfunction] +pub(crate) fn demo_replay_ssm(py: Python, passos: usize, alpha: f64) -> PyResult { + let dev = candle_core::Device::Cpu; + let mut ssm = jepa::ReplaySsm::novo(4, 6, alpha, 8, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut otim = crate::treino::AdamWManual::novo(ssm.vars(), 1e-2, 0.0); + let x_antigo = candle_core::Tensor::from_vec(vec![1.0f32; 16 * 4], (16, 4), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut hist = Vec::new(); + for _ in 0..passos { + let h = ssm.scan(&x_antigo).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let alvo = h.detach(); + let (pt, pn, pr) = ssm.passo_treino(&x_antigo, &alvo, &mut otim) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + hist.push(serde_json::json!({"total": pt, "novo": pn, "replay": pr})); + } + let x_novo = candle_core::Tensor::from_vec(vec![-1.0f32; 16 * 4], (16, 4), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + for _ in 0..passos { + let h = ssm.scan(&x_novo).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let alvo = h.detach(); + let _ = ssm.passo_treino(&x_novo, &alvo, &mut otim) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + } + let h_fin = ssm.scan(&x_antigo).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mag = h_fin.sqr().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? + .max_all().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? + .to_scalar::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? as f64; + let d_ = serde_json::json!({ + "versao": jepa::versao(), + "alpha_replay": alpha, + "historico": hist, + "magnitudes_finitas": mag.is_finite(), + "magnitude_max_pos_troca": mag, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) +} + + +/// Extrai patches 2D de uma imagem achatada (C, H, W) — adaptador Image-JEPA. +#[pyfunction] +pub(crate) fn patches_2d(img: Vec, c: usize, h: usize, w: usize, ph: usize, pw: usize) -> PyResult>> { + let dev = candle_core::Device::Cpu; + let t = candle_core::Tensor::from_vec(img, (c, h, w), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let p = jepa::em_patches_2d(&t, ph, pw) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + p.to_vec2::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))) +} + + +/// Extrai patches canal-independentes de série temporal (C, T) — T-JEPA. +#[pyfunction] +pub(crate) fn patches_serie(ts: Vec, c: usize, t: usize, pt: usize) -> PyResult>> { + let dev = candle_core::Device::Cpu; + let tt = candle_core::Tensor::from_vec(ts, (c, t), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let p = jepa::em_patches_serie(&tt, pt) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + p.to_vec2::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))) +} + + +/// Extrai grupos FPS de nuvem de pontos (n, d) — Point-JEPA. +#[pyfunction] +pub(crate) fn grupos_pontos(pc: Vec>, k: usize, m: usize, semente: u64) -> PyResult>> { + let dev = candle_core::Device::Cpu; + let t = tensor_de_2d(pc, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut rng = crate::rng::RngDeterministico::novo(semente); + let g = jepa::em_grupos_pontos(&t, k, m, &mut rng) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let out = g.to_vec2::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + Ok(out.into_iter().map(|l| l.into_iter().map(|v| v as f64).collect()).collect()) +} diff --git a/src/python/mod.rs b/src/python/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..53800191d7c6cb5efa8830d19dc880baa2938cfb --- /dev/null +++ b/src/python/mod.rs @@ -0,0 +1,145 @@ +//! FFI Python (PyO3) — dividida por domínio (v0.10.0, r-35 §2). + +//! FFI Python (PyO3) do KHTST_RUST — todo o `#[pymodule]`, os +//! `#[pyfunction]`/`#[pyclass]` e a cola Python (conversões, +//! callbacks, demos) vivem AQUI desde a v0.9.0 (antes: lib.rs, +//! ~105 kB — crítica "God Object" do agente Qwen3.8, r-34 §5). +//! A lógica de domínio (src/*.rs) não importa pyo3: `cargo test +//! --no-default-features` executa a suíte SEM linkar o Python. +//! +//! Regra da casa mantida: nenhuma funcionalidade da superfície +//! Python foi removida — os mesmos símbolos são registrados no +//! `nucleo_rust`. + +use pyo3::prelude::*; + +use crate::*; + +// ---- divisão SRP v0.10.0 (r-35 §2): submódulos + re-exports ---- +mod calibracao; +mod demos_qwen; +mod fnh_arvore; +mod jepa_treinos; +mod moe; +mod nlp_nlg; +mod nucleo; +mod pensamento_demos; +mod som; +mod treino_fases; + + +// ---- itens mantidos no próprio mod.rs ---- + + + +/// Adaptador FFI→domínio (v0.9.0): embrulha um callable Python no tipo de +/// domínio `treino::CallbackTreino` — `treino.rs` não importa pyo3. +fn callback_treino_de_py(callback: Option>) -> treino::CallbackTreino { + use std::sync::Arc; + match callback { + None => None, + Some(c) => { + let f: pyo3::Py = c.unbind(); + let g: Arc = + Arc::new(move |dados: &serde_json::Value| { + pyo3::Python::with_gil(|py| { + if let Ok(obj) = pythonize::pythonize(py, dados) { + let _ = f.call1(py, (obj,)); + } + }); + }); + Some(g) + } + } +} + + +// =========================================================================== +// v0.5.0 — suíte SOM 4D, suíte JEPA, Pensamento, telemetria robusta +// =========================================================================== + +fn tensor_de_2d(dados: Vec>, dev: &candle_core::Device) -> candle_core::Result { + let d = dados.first().map(|r| r.len()).unwrap_or(1).max(1); + let n = dados.len(); + let flat: Vec = dados.into_iter().flat_map(|r| r.into_iter().map(|x| x as f32).collect::>()).collect(); + candle_core::Tensor::from_vec(flat, (n, d), dev) +} + + +// --------------------------------------------------------------------------- +// v0.6.0 — verificador NCM (sem oráculo), fase C, MoE redirecionável, +// acoplador duplex e autoajuste +// --------------------------------------------------------------------------- + +/// Converte matriz f64 → Tensor (n, d) F32. +fn tensor_f64_2d(v: &[Vec], dev: &candle_core::Device) -> candle_core::Result { + let (n, d) = (v.len(), v.first().map(|l| l.len()).unwrap_or(0)); + candle_core::Tensor::from_vec(v.iter().flatten().cloned().collect(), (n, d), dev) +} + + +fn py_err(e: candle_core::Error) -> pyo3::PyErr { + pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")) +} + + +#[pymodule] +fn nucleo_rust(m: &Bound<'_, PyModule>) -> PyResult<()> { + pyo3_log::init(); + m.add_function(wrap_pyfunction!(nucleo::versao, m)?)?; + m.add_function(wrap_pyfunction!(nucleo::info_build, m)?)?; + m.add_function(wrap_pyfunction!(nucleo::lint_config, m)?)?; + m.add_function(wrap_pyfunction!(treino_fases::treinar_fase_a, m)?)?; + m.add_function(wrap_pyfunction!(treino_fases::treinar_fase_b, m)?)?; + m.add_function(wrap_pyfunction!(treino_fases::gerar_amostras, m)?)?; + m.add_function(wrap_pyfunction!(nucleo::verificar_mascara, m)?)?; + m.add_function(wrap_pyfunction!(nucleo::verificar_stiefel, m)?)?; + // v0.10.0 — checkpoints atômicos com prova de integridade + m.add_function(wrap_pyfunction!(nucleo::checkpoint_valido, m)?)?; + m.add_function(wrap_pyfunction!(nucleo::checkpoint_manifesto, m)?)?; + m.add_function(wrap_pyfunction!(nucleo::sha256_arquivo, m)?)?; + // v0.5.0 — SOM 4D / JEPA / Pensamento + m.add_function(wrap_pyfunction!(som::treinar_som4d, m)?)?; + m.add_function(wrap_pyfunction!(som::treinar_gng, m)?)?; + m.add_function(wrap_pyfunction!(som::treinar_hsom, m)?)?; + m.add_function(wrap_pyfunction!(som::demo_tsom, m)?)?; + m.add_function(wrap_pyfunction!(jepa_treinos::treinar_jepa, m)?)?; + m.add_function(wrap_pyfunction!(jepa_treinos::treinar_lejepa, m)?)?; + m.add_function(wrap_pyfunction!(jepa_treinos::treinar_mcjepa, m)?)?; + m.add_function(wrap_pyfunction!(jepa_treinos::treinar_bjepa, m)?)?; + m.add_function(wrap_pyfunction!(jepa_treinos::demo_sonic_moe, m)?)?; + m.add_function(wrap_pyfunction!(jepa_treinos::demo_replay_ssm, m)?)?; + m.add_function(wrap_pyfunction!(pensamento_demos::classificar_erro, m)?)?; + m.add_function(wrap_pyfunction!(pensamento_demos::confianca_de_logits, m)?)?; + m.add_function(wrap_pyfunction!(pensamento_demos::projetar_ridge, m)?)?; + m.add_function(wrap_pyfunction!(pensamento_demos::demo_pensamento, m)?)?; + m.add_function(wrap_pyfunction!(jepa_treinos::patches_2d, m)?)?; + m.add_function(wrap_pyfunction!(jepa_treinos::patches_serie, m)?)?; + m.add_function(wrap_pyfunction!(jepa_treinos::grupos_pontos, m)?)?; + // v0.6.0 + m.add_function(wrap_pyfunction!(calibracao::calibrar_verificador, m)?)?; + m.add_function(wrap_pyfunction!(calibracao::demo_verificador_ncm, m)?)?; + m.add_function(wrap_pyfunction!(treino_fases::treinar_fase_c, m)?)?; + m.add_function(wrap_pyfunction!(moe::demo_moe_redirecionavel, m)?)?; + m.add_function(wrap_pyfunction!(moe::jepa_com_moe, m)?)?; + m.add_function(wrap_pyfunction!(moe::som_com_moe, m)?)?; + m.add_function(wrap_pyfunction!(moe::demo_acoplador_duplo, m)?)?; + m.add_function(wrap_pyfunction!(pensamento_demos::demo_pensamento_ncm, m)?)?; + m.add_function(wrap_pyfunction!(pensamento_demos::demo_autoajuste, m)?)?; + m.add_function(wrap_pyfunction!(fnh_arvore::demo_fnh, m)?)?; + m.add_function(wrap_pyfunction!(fnh_arvore::demo_quebra_cabeca, m)?)?; + m.add_function(wrap_pyfunction!(fnh_arvore::quebra_cabeca_real, m)?)?; + m.add_function(wrap_pyfunction!(nlp_nlg::demo_nlp_nlg, m)?)?; + m.add_function(wrap_pyfunction!(som::demo_autoajuste_som, m)?)?; + // v0.8.0 — análise/prova/teste das propostas Qwen3.8 + m.add_function(wrap_pyfunction!(demos_qwen::demo_qwen_v080, m)?)?; + // v0.9.0 — veredito executável das críticas Qwen3.8 (r-34) + m.add_function(wrap_pyfunction!(demos_qwen::demo_criticas_v090, m)?)?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + #[cfg(feature = "ponte_async")] + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/src/python/moe.rs b/src/python/moe.rs new file mode 100644 index 0000000000000000000000000000000000000000..e0806c8d0f1d5af7a45653a3a3139eca48a58def --- /dev/null +++ b/src/python/moe.rs @@ -0,0 +1,190 @@ +//! `python::moe` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +/// **MoE redirecionável — demo completa (M-R1..M-R5):** neutralidade da +/// atenção própria (M-R2), invariância de capacidade/perda auxiliar sob +/// redirecionamento (M-R1), paralelismo medido (M-R3), water-filling por +/// QE vs uniforme (M-R5) e fechamento da classe (M-R4 — via permutação). +#[pyfunction] +pub(crate) fn demo_moe_redirecionavel(d: usize, n: usize, semente: u64) -> PyResult { + Python::with_gil(|py| { + let dev = candle_core::Device::Cpu; + crate::rng::definir_semente(semente); + let mut rng = crate::rng::RngDeterministico::novo(semente); + let x: Vec = (0..n * d).map(|_| rng.normal(0.0, 1.0) as f32).collect(); + let x = candle_core::Tensor::from_vec(x, (n, d), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut moe = moe_redirecionavel::MoeRedirecionavel::novo(d, 8, 2, n, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + // M-R2: neutralidade no nascimento + let g0 = moe.atencao.gate.as_tensor().to_scalar::().unwrap(); + let y0 = moe.atencao.fwd(&x).unwrap(); + let dif0 = (&y0 - &x).unwrap().sqr().unwrap().sum_all().unwrap() + .to_scalar::().unwrap() as f64; + // abre o gate (mecanismo ativo) + moe.atencao.gate.set(&candle_core::Tensor::new(1.0f32, &dev).unwrap()).unwrap(); + // M-R1: invariância sob redirecionamento + let s_enc = moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap(); + let carga_antes: usize = s_enc.carga.iter().sum(); + let perda_antes = s_enc.perda_aux.to_scalar::().unwrap() as f64; + let permuta: Vec = (0..8).map(|i| (i + 1) % 8).collect(); + moe.redirecionar(&permuta); + let s_enc2 = moe.forward(&x, moe_redirecionavel::Papel::Codificador).unwrap(); + let carga_depois: usize = s_enc2.carga.iter().sum(); + let perda_depois = s_enc2.perda_aux.to_scalar::().unwrap() as f64; + // M-R3: paralelismo medido (enc ∥ dec em 2 threads) + let (_e, _dc, t_par, t_seq, speedup) = moe.fwd_ambos_par(&x).unwrap(); + // M-R5: water-filling vs uniforme (cota analítica) + let qes: Vec = vec![0.15, 0.4, 1.0, 0.25, 0.6, 0.1, 0.35, 0.5]; + let c_wf = moe_redirecionavel::alocar_por_qe(&qes, 16); + let c_un = vec![2usize; 8]; + let cota = |c: &[usize]| qes.iter().zip(c).map(|(q, ci)| q * q / *ci as f64).sum::(); + let (cota_wf, cota_un) = (cota(&c_wf), cota(&c_un)); + let d_ = serde_json::json!({ + "versao": moe_redirecionavel::versao(), "d": d, "n": n, + "valor_gate_no_nascimento": g0, "neutralidade_dif": dif0, + "carga_antes": carga_antes, "carga_depois": carga_depois, + "perda_aux_antes": perda_antes, "perda_aux_depois": perda_depois, + "t_paralelo_s": t_par, "t_sequencial_s": t_seq, "speedup": speedup, + "alocacao_water_filling": c_wf, "cota_wf": cota_wf, "cota_uniforme": cota_un, + "gates": { + "gate_mr1_capacidade": carga_antes == carga_depois, + "gate_mr1_perda_aux": (perda_antes - perda_depois).abs() < 2.0 + 1e-6, + "gate_mr2_neutralidade": dif0 == 0.0 && g0 == 0.0, + "gate_mr3_paralelismo": t_par < t_seq, + "gate_mr5_water_filling": cota_wf <= cota_un + 1e-9, + }, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) + }) +} + + +/// **JEPA com MoE redirecionável (ablação de 3 braços):** `modo` ∈ +/// {"pequena", "densa", "moe"} — o braço "densa" tem a capacidade TOTAL dos +/// especialistas SEM roteamento (isola o ganho do roteamento em si). +#[pyfunction] +pub(crate) fn jepa_com_moe( + patches: Vec>, + modo: String, + passos: usize, + frac_mascara: f64, + semente: u64, +) -> PyResult { + Python::with_gil(|py| { + let dev = candle_core::Device::Cpu; + let t = tensor_f64_2d(&patches.into_iter().map(|l| l.into_iter().map(|x| x as f64).collect()).collect::>>(), &dev) + .and_then(|t| t.to_dtype(candle_core::DType::F32)) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let modo: &'static str = match modo.as_str() { + "pequena" => "pequena", + "densa" => "densa", + _ => "moe", + }; + let r = moe_redirecionavel::treinar_jepa_com_moe(&t, modo, passos, frac_mascara, semente) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let d_ = serde_json::json!({ + "modo": r.modo, "perda_pred_inicial": r.perda_pred_inicial, + "perda_pred_final": r.perda_pred_final, "perda_vc_final": r.perda_vc_final, + "std_emb": r.std_emb, "carga_max": r.carga_max, "descartados": r.descartados, + "queda_abs": r.perda_pred_inicial - r.perda_pred_final, + "aprendeu": r.perda_pred_final < r.perda_pred_inicial, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) + }) +} + + +/// **SOM refinado por MoE com water-filling (M-R5) + redirecionamento +/// para decodificador:** treina um 4D-SOM, refina com MoE (QE′ < QE), +/// compara com alocação uniforme e mede a decodificação pós-redirect. +#[pyfunction] +pub(crate) fn som_com_moe( + dados: Vec>, + lado: usize, + passos: usize, + semente: u64, +) -> PyResult { + Python::with_gil(|py| { + let dev = candle_core::Device::Cpu; + let t = tensor_f64_2d(&dados, &dev) + .and_then(|t| t.to_dtype(candle_core::DType::F32)) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + // treina o 4D-SOM (r-19: S4-1/S4-2/S4-3) — som4d opera em F32 + let cfg_som = som4d::CfgSom4D { nx: lado, ny: lado, nz: lado, nt: lado, ..Default::default() }; + let som = som4d::treinar_som4d(&t, &cfg_som, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let centroides: Vec> = som.prototipos.clone(); + // refinamento MoE (mesmo tensor F32; centroides seguem em f64) + let r = moe_redirecionavel::refinar_som_com_moe(&t, ¢roides, passos, semente) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let d_ = serde_json::json!({ + "qe_antes": r.qe_antes, "qe_depois_water_filling": r.qe_depois, + "qe_depois_uniforme": r.qe_alocacao_uniforme, + "carga_por_celula": r.carga_por_celula, + "tempo_treino_s": r.tempo_treino_s, + "mse_decodificador_pos_redirect": r.mse_decodificador, + "reducao_qe_pct": 100.0 * (r.qe_antes - r.qe_depois) / r.qe_antes.max(1e-12), + "gates": { + "gate_moe_refina": r.qe_depois < r.qe_antes, + "gate_wf_melhor_uniforme": r.qe_depois <= r.qe_alocacao_uniforme * 1.02, + "gate_redirect_decodifica": r.mse_decodificador < r.qe_antes.powi(2), + }, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) + }) +} + + +/// **AcopladorDuplo — demo do protocolo lag-1 síncrono** (AC-1/AC-2): +/// rodadas duplex Pensamento↔backbone com correções decaindo (co-adaptação) +/// e confiança subindo; zero deadlocks/timeouts (P.1/P.2 herdados). +#[pyfunction] +pub(crate) fn demo_acoplador_duplo(rodadas: usize, timeout_ms: u64) -> PyResult { + Python::with_gil(|py| { + let mut ac = acoplador::AcopladorDuplo::nova(16, timeout_ms); + let mut rng = crate::rng::RngDeterministico::novo(77); + let mut conf = 0.05f64; + let mut traj = Vec::new(); + for k in 0..rodadas { + let estado = acoplador::EstadoModelo { + a: vec![0.1 * (k % 7) as f32; 16], + b: vec![rng.normal(0.0, 0.3) as f32; 16], + c: vec![0.05; 8], + confianca: conf, + nivel: k % 7, + passo: k, + }; + let _ = ac.rodada(estado).map_err(|e| + pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + // correção fresca: decaindo com a confiança (co-adaptação) + let amplitude = (1.0 - conf).max(0.05); + let delta: Vec = (0..16).map(|i| amplitude as f32 * ((i % 5) as f32 - 2.0) * 0.1).collect(); + ac.registrar_correcao(acoplador::Correcao { + delta, acordo: 0.6 + 0.3 * conf, confianca_agregada: conf, + iteracao: k, pesos: vec![0.25; 4], + }); + traj.push(conf); + conf = (conf + 0.04 + rng.uniforme() * 0.02).min(0.99); + } + let t = &ac.telemetria; + let p = ac.estatisticas(); + let d_ = serde_json::json!({ + "versao": acoplador::versao(), "rodadas": t.rodadas, + "timeouts": t.timeouts, "tempo_total_ms": t.tempo_total_ms, + "soma_norma_delta": t.soma_norma_delta, + "conf_final": traj.last(), "conf_trajetoria_len": traj.len(), + "envios_a_para_b": p.envios_a_para_b, "envios_b_para_a": p.envios_b_para_a, + "maior_sobreposicao_us": t.maior_sobreposicao_us, + "gates": { + "gate_terminou": t.rodadas == rodadas as u64, + "gate_sem_timeout": t.timeouts == 0, + "gate_duplex_simultaneo": p.envios_a_para_b == p.envios_b_para_a && p.envios_a_para_b == rodadas as u64, + }, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) + }) +} diff --git a/src/python/nlp_nlg.rs b/src/python/nlp_nlg.rs new file mode 100644 index 0000000000000000000000000000000000000000..88fc9a68b2979e765d4576559cab4f706ac0a8ea --- /dev/null +++ b/src/python/nlp_nlg.rs @@ -0,0 +1,69 @@ +//! `python::nlp_nlg` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +/// Demo/gate das unidades NLP/NLG sobre estados ocultos sintéticos +/// (nascimento neutro + gates + coerência de bigramas + especulativo). +#[pyfunction] +#[pyo3(signature = (d=32, t=6, seed=11))] +pub(crate) fn demo_nlp_nlg(py: Python, d: usize, t: usize, seed: u64) -> PyResult { + let dev = candle_core::Device::Cpu; + let mut rng = rng::RngDeterministico::novo(seed); + let v: Vec = (0..2 * t * d).map(|_| rng.normal_padrao() as f32).collect(); + let h = candle_core::Tensor::from_vec(v, (2, t, d), &dev).map_err(py_err)?; + // NLP: enriquecimento aditivo — neutro no nascimento + let mut nlp = nlp::UnidadeNlp::nova(d, &dev).map_err(|e| { + pyo3::exceptions::PyRuntimeError::new_err(format!("nlp (d%4): {e}")) + })?; + let h_enr = nlp.enriquecer(&h).map_err(py_err)?; + let dif_nlp = (&h).sub(&h_enr).map_err(py_err)?.abs().map_err(py_err)?.max_all().map_err(py_err)?.to_scalar::().map_err(py_err)? as f64; + // intenção + let (intencoes, confs) = nlp.intencao_argmax(&h).map_err(py_err)?; + // NLG: plano + condicionamento neutro + coerência + let mut nlg = nlg::UnidadeNlg::nova(d, 4096, &dev).map_err(py_err)?; + let plano = nlg.plano(&h).map_err(py_err)?; + let p8 = candle_core::Tensor::randn(0f32, 1f32, (nlg::N_EIXOS_ESTILO,), &dev).map_err(py_err)?; + let p01 = candle_nn::ops::sigmoid(&p8).map_err(py_err)?; + let h_cond = nlg.condicionar(&h, Some(&p01)).map_err(py_err)?; + let dif_nlg = (&h).sub(&h_cond).map_err(py_err)?.abs().map_err(py_err)?.max_all().map_err(py_err)?.to_scalar::().map_err(py_err)? as f64; + // coerência: contexto observado vs candidato novo + let ctx = vec![5u32, 6, 7, 8, 9]; + nlg.observar_contexto(&ctx); + let s_mesmo = nlg.score_coerencia(&ctx); + let s_novo = nlg.score_coerencia(&[50u32, 51, 52, 53]); + // especulativo: aceitação típica + stats + let mut espec = nlg::DecodificadorEspeculativo::novo(4, 0.95, 10); + let logits = candle_core::Tensor::from_vec(vec![3.0f32, 0.2, 0.1, 0.05], (4,), &dev) + .map_err(py_err)?; + let mut aceites = 0usize; + for _ in 0..30 { + let (_, ok) = espec.verificar_e_corrigir(&logits, &[0, 1]).map_err(py_err)?; + if ok { + aceites += 1; + } + } + let d_ = serde_json::json!({ + "versao": "nlp-nlg-0.7.0", + "nlp_gate_medio": nlp.gate_medio, + "nlp_dif_nascimento": dif_nlp, + "nlp_intencoes": intencoes, + "nlp_confiancas": confs, + "nlg_plano_forma": plano.dims(), + "nlg_dif_nascimento": dif_nlg, + "nlg_coerencia_mesmo": s_mesmo, + "nlg_coerencia_novo": s_novo, + "espec_aceitacao_media": espec.stats.aceitacao_media, + "espec_speedup": espec.stats.speedup_estimado, + "espec_aceites_30": aceites, + "gates": { + "nlp_neutro": dif_nlp == 0.0, + "nlg_neutro": dif_nlg == 0.0, + "gate_medio_ok": (nlp.gate_medio - 0.119).abs() < 0.05, + "coerencia_ok": s_mesmo > 0.9 && s_novo < 0.1, + "espec_ok": espec.stats.aceitacao_media > 0.8, + }, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) +} diff --git a/src/python/nucleo.rs b/src/python/nucleo.rs new file mode 100644 index 0000000000000000000000000000000000000000..1d55e570fea97e1361b54df48dfeb4d60466fed2 --- /dev/null +++ b/src/python/nucleo.rs @@ -0,0 +1,239 @@ +//! `python::nucleo` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +#[pyfunction] +pub(crate) fn versao() -> &'static str { + concat!("khtst-rust-", env!("CARGO_PKG_VERSION")) +} + + +#[pyfunction] +pub(crate) fn info_build() -> String { + format!( + "rustc={}; perfil={}; features={}; alvo={}", + build_info::RUSTC_VERSION, + build_info::PROFILE, + build_info::FEATURES_STR, + build_info::TARGET, + ) +} + + +/// Lint de config com sugestões por Levenshtein (diagnóstico de digitação). +#[pyfunction] +pub(crate) fn lint_config(py: Python, caminho: &str) -> PyResult { + let txt = std::fs::read_to_string(caminho) + .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{caminho}: {e}")))?; + let v: serde_json::Value = serde_json::from_str(&txt) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?; + let achatado = diagnostico::achatar(&v); + let avisos = diagnostico::lint_chaves(&achatado); + let saida: Vec = avisos + .iter() + .map(|a| serde_json::json!({ + "chave": a.chave, "sugestao": a.sugestao, "causa": a.causa, "correcao": a.correcao, + })) + .collect(); + Ok(pythonize::pythonize(py, &serde_json::json!(saida))?.into()) +} + + +#[pyclass(name = "Bpe")] +pub(crate) struct BpePy { + bpe: tokens::Bpe, +} + +#[pymethods] +impl BpePy { + #[staticmethod] + fn treinar(textos: Vec, vocab_alvo: usize) -> PyResult { + Ok(BpePy { bpe: tokens::Bpe::treinar(&textos, vocab_alvo) }) + } + + #[staticmethod] + fn carregar(caminho: &str) -> PyResult { + let txt = std::fs::read_to_string(caminho) + .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{e}")))?; + let v: serde_json::Value = serde_json::from_str(&txt) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?; + let bpe = tokens::Bpe::de_json(&v) + .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("formato BPE inválido"))?; + Ok(BpePy { bpe }) + } + + fn codificar(&mut self, texto: &str) -> Vec { + self.bpe.codificar(texto) + } + + fn decodificar(&self, ids: Vec) -> String { + self.bpe.decodificar(&ids) + } + + fn salvar(&self, caminho: &str) -> PyResult<()> { + let txt = serde_json::to_string(&self.bpe.para_json()).unwrap(); + crate::checkpoint::salvar_atomico(caminho, &txt) + .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{e}")))?; + Ok(()) + } + + #[getter] + fn n_vocab(&self) -> usize { + self.bpe.n_vocab() + } +} + + +#[pyclass(name = "Config")] +pub(crate) struct ConfigPy { + pub(crate) cfg: config::ConfigKhtst, + caminho: Option, +} + +#[pymethods] +impl ConfigPy { + #[new] + #[allow(never_type_fallback)] + #[pyo3(signature = (caminho=None))] + fn new(caminho: Option) -> PyResult { + let cfg = match &caminho { + Some(c) => { + let txt = std::fs::read_to_string(c) + .map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{c}: {e}")))?; + let v: serde_json::Value = serde_json::from_str(&txt) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?; + serde_json::from_value(v) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("config: {e}")))? + } + None => config::ConfigKhtst::default(), + }; + cfg.validar().map_err(pyo3::exceptions::PyValueError::new_err)?; + Ok(ConfigPy { cfg, caminho }) + } + + fn validar(&self) -> PyResult<()> { + self.cfg.validar().map_err(pyo3::exceptions::PyValueError::new_err) + } + + fn checar_orcamento_cache(&self) -> PyResult { + self.cfg.checar_orcamento_cache().map_err(pyo3::exceptions::PyValueError::new_err) + } + + fn resumo(&self) -> String { + format!( + "modelo(vocab={}, d={}, camadas={}, ctx={}) ncm(L={}, niveis={:?}, K={}, d_lat={}, modo={}, ativo={})", + self.cfg.modelo.vocab, self.cfg.modelo.d_modelo, self.cfg.modelo.n_camadas, + self.cfg.modelo.comprimento_ctx, self.cfg.l_niveis(), self.cfg.ncm.niveis, + self.cfg.ncm.k_por_nivel, self.cfg.ncm.d_lat, self.cfg.ncm.modo_quantizacao, self.cfg.ncm.ativo, + ) + } + + #[getter] + fn l_niveis(&self) -> usize { + self.cfg.l_niveis() + } +} + + +#[pyclass(name = "PonteDupla")] +pub(crate) struct PonteDuplaPy { + ponte: ponte::PonteDupla, +} + +#[pymethods] +impl PonteDuplaPy { + #[new] + #[pyo3(signature = (capacidade=64, timeout_ms=2000))] + fn new(capacidade: usize, timeout_ms: u64) -> Self { + PonteDuplaPy { ponte: ponte::PonteDupla::nova(capacidade, timeout_ms) } + } + + fn rodada(&self, carga_a: Vec, carga_b: Vec, nivel: usize) -> PyResult<(Vec, Vec)> { + let (ma, mb) = self.ponte.rodada(carga_a, carga_b, nivel) + .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; + Ok((ma.carga, mb.carga)) + } + + fn demo_professor_aluno(&self, rodadas: usize, dim: usize) -> PyResult { + let inicio: Vec = (0..dim).map(|i| (i as f32) * 0.01).collect(); + let stats = ponte::demo_professor_aluno(&self.ponte, rodadas, inicio, &|a, _| { + a.iter().map(|x| x * 1.05).collect() + }) + .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; + Python::with_gil(|py| { + Ok(pythonize::pythonize(py, &serde_json::to_value(&stats).unwrap())?.into()) + }) + } + + fn estatisticas(&self) -> PyResult { + let s = self.ponte.estatisticas(); + Python::with_gil(|py| { + Ok(pythonize::pythonize(py, &serde_json::to_value(&s).unwrap())?.into()) + }) + } +} + + +#[cfg(feature = "ponte_async")] +#[pyclass(name = "PonteAsync")] +pub(crate) struct PonteAsyncPy { + interna: std::sync::Arc, +} + +#[cfg(feature = "ponte_async")] +#[pymethods] +impl PonteAsyncPy { + #[new] + #[pyo3(signature = (capacidade=64, timeout_ms=2000))] + fn new(capacidade: usize, timeout_ms: u64) -> Self { + PonteAsyncPy { interna: std::sync::Arc::new(ponte::assincrona::PonteAsync::nova(capacidade, timeout_ms)) } + } + + /// Corrotina: `await ponte.trocar(a, b, nivel)` — duplex simultânea. + fn trocar<'p>(&self, py: Python<'p>, carga_a: Vec, carga_b: Vec, nivel: usize) -> PyResult> { + let p = self.interna.clone(); + pyo3_async_runtimes::tokio::future_into_py(py, async move { + let (ma, mb) = p.rodada_async(carga_a, carga_b, nivel) + .await + .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; + Ok::<_, PyErr>((ma.carga, mb.carga)) + }) + } +} + + +/// Utilitários de prova expostos ao Python (V-série da suíte). +#[pyfunction] +pub(crate) fn verificar_mascara(cfg: &ConfigPy) -> PyResult { + let dev = candle_core::Device::Cpu; + let m = mascaras::mascara_block_causal(&cfg.cfg.ncm.niveis, gerador::N_PREFIXO, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + mascaras::contar_vazamentos(&cfg.cfg.ncm.niveis, gerador::N_PREFIXO, &m) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))) +} + + +#[pyfunction] +pub(crate) fn verificar_stiefel(k: usize, d: usize) -> PyResult { + let dev = candle_core::Device::Cpu; + let c = stiefel::init_stiefel(k, d, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + stiefel::erro_ortogonalidade(&c.transpose(0, 1).unwrap()).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))) +} + +#[pyfunction] +pub(crate) fn checkpoint_valido(caminho: &str) -> bool { + crate::checkpoint::validar(caminho) +} + +#[pyfunction] +pub(crate) fn checkpoint_manifesto(caminho: &str) -> Option { + std::fs::read_to_string(crate::checkpoint::caminho_manifesto(caminho)).ok() +} + +#[pyfunction] +pub(crate) fn sha256_arquivo(caminho: &str) -> Option { + std::fs::read(caminho).ok().map(|d| crate::checkpoint::sha256_hex(&d)) +} diff --git a/src/python/pensamento_demos.rs b/src/python/pensamento_demos.rs new file mode 100644 index 0000000000000000000000000000000000000000..381fac2d50506608538a9f5418a023f150798fec --- /dev/null +++ b/src/python/pensamento_demos.rs @@ -0,0 +1,367 @@ +//! `python::pensamento_demos` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +/// Classificação de erro por taxonomia (diagnóstico de refatoração). +#[pyfunction] +pub(crate) fn classificar_erro(msg: String) -> Vec<(String, String, String)> { + diagnostico::classificar_erro(&msg) + .into_iter() + .map(|(a, b, c)| (a.to_string(), b.to_string(), c.to_string())) + .collect() +} + + +/// Confiança de uma distribuição (T-P1): `1 − H(softmax(z))/log K`. +#[pyfunction] +pub(crate) fn confianca_de_logits(logits: Vec) -> PyResult { + let dev = candle_core::Device::Cpu; + let k = logits.len(); + let t = candle_core::Tensor::from_vec(logits, (1, k), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + pensamento::confianca_de_logits(&t).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}"))) +} + + +/// Projeção ridge (T-P3 — montador algébrico): `r* = (WᵀW+εI)⁻¹Wᵀr`. +#[pyfunction] +pub(crate) fn projetar_ridge(w: Vec>, r_alvo: Vec>, eps: f64) -> PyResult>> { + let dev = candle_core::Device::Cpu; + let wt = tensor_de_2d(w, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let rt = tensor_de_2d(r_alvo, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let r = pensamento::projetar_ridge(&wt, &rt, eps) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let out = r.to_vec2::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + Ok(out.into_iter().map(|l| l.into_iter().map(|v| v as f64).collect()).collect()) +} + + +/// Demo completa da tecnologia Pensamento: treino cooperativo das N redes +/// (item a) + ciclo d com verificador externo simulado + telemetria. +#[pyfunction] +pub(crate) fn demo_pensamento( + py: Python, + d_cond: usize, + d_saida: usize, + n_tentativas: usize, + passos_treino: usize, + limiar_conf: f64, + orcamento: usize, + semente: u64, +) -> PyResult { + use candle_core::Tensor; + let dev = candle_core::Device::Cpu; + crate::rng::definir_semente(semente); + let mut motor = pensamento::MotorPensamento::novo(d_cond, d_saida, n_tentativas, limiar_conf, orcamento, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut otim = crate::treino::AdamWManual::novo(motor.vars(), 5e-3, 0.0); + let mut rng = crate::rng::RngDeterministico::novo(semente); + + // dados sintéticos: alvo = função determinística não-linear de cond (d=a+b+c) + let n_dados = 256usize; + let conds: Vec> = (0..n_dados) + .map(|_| (0..d_cond).map(|_| rng.normal(0.0, 1.0) as f32).collect()) + .collect(); + let alvos: Vec> = conds + .iter() + .map(|c| { + (0..d_saida) + .map(|j| { + let cj = c[j % d_cond]; + let ck = c[(j + 1) % d_cond]; + 0.5 * cj + 0.3 * cj * ck + 0.1 + }) + .collect() + }) + .collect(); + let cond_t = Tensor::from_vec(conds.into_iter().flatten().collect(), (n_dados, d_cond), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let alvo_t = Tensor::from_vec(alvos.into_iter().flatten().collect(), (n_dados, d_saida), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + + // item a: treino cooperativo + let mut perda_hist = Vec::new(); + for passo in 0..passos_treino { + let perda = motor.perda_treino(&cond_t, &alvo_t) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + perda_hist.push(perda.to_scalar::().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? as f64); + otim.passo(crate::treino::grads_achatados(&perda, &motor.vars()) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?); + let _ = passo; + } + + // item c+d: consulta fora do padrão (OOD) — confiança baixa ⇒ Pensamento + let consulta: Vec = (0..d_cond).map(|j| 3.0 + (j as f32) * 0.1).collect(); + let consulta_t = Tensor::from_vec(consulta, (d_cond,), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + // verificador externo: negativo do MSE contra o alvo verdadeiro da consulta + let alvo_verdadeiro: Vec = (0..d_saida) + .map(|j| { + let cj = 3.0 + (j % d_cond) as f32 * 0.1; + let ck = 3.0 + ((j + 1) % d_cond) as f32 * 0.1; + 0.5 * cj + 0.3 * cj * ck + 0.1 + }) + .collect(); + let alvo_verd = Tensor::from_vec(alvo_verdadeiro.clone(), (d_saida,), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let verifica = |resp: &Tensor| -> f64 { + // confiança = 1/(1+10·MSE) — medível e causaL (só da resposta) + let dif = (resp.broadcast_as((1, d_saida)).unwrap() - &alvo_verd.broadcast_as((1, d_saida)).unwrap()).unwrap(); + let mse = dif.sqr().unwrap().mean_all().unwrap().to_scalar::().unwrap() as f64; + 1.0 / (1.0 + 10.0 * mse) + }; + let mut rng2 = crate::rng::RngDeterministico::novo(semente + 1); + let res = motor.raciocinar(&consulta_t, &verifica, &mut rng2) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + + // linha de base SEM pensamento: uma tentativa isolada, sem agregação + let base = motor.tentativas[0].fwd(&consulta_t.broadcast_as((1, d_cond)).unwrap()) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let conf_base = verifica(&base); + let conf_pensamento = res.confianca; + + let d_ = serde_json::json!({ + "versao": pensamento::versao(), + "n_tentativas": n_tentativas, + "perda_treino_inicial": perda_hist.first(), + "perda_treino_final": perda_hist.last(), + "treino_aprendeu": perda_hist.last().copied().unwrap_or(f64::INFINITY) < perda_hist.first().copied().map(|p| p * 0.5).unwrap_or(f64::INFINITY), + "iteracoes_ciclo_d": res.iteracoes, + "confianca_base_1_rede": conf_base, + "confianca_pensamento": conf_pensamento, + "ganho_confianca": conf_pensamento - conf_base, + "orcamento_esgotado": res.telemetria.get("orcamento_esgotado").copied().unwrap_or(0.0), + "telemetria": res.telemetria, + "eventos_ciclo": res.trajetoria.iter().map(|e| match e { + pensamento::EventoCiclo::Planejamento { iteracao, temperatura } => + serde_json::json!({"tipo": "planejamento", "iteracao": iteracao, "temperatura": temperatura}), + pensamento::EventoCiclo::DistribuicaoCompetencias { por_tentativa } => + serde_json::json!({"tipo": "distribuicao", "pesos": por_tentativa}), + pensamento::EventoCiclo::RePlanejamento { motivo } => + serde_json::json!({"tipo": "replanejamento", "motivo": motivo}), + pensamento::EventoCiclo::Resposta { confianca, iteracoes } => + serde_json::json!({"tipo": "resposta", "confianca": confianca, "iteracoes": iteracoes}), + }).collect::>(), + }); + Ok(pythonize::pythonize(py, &d_)?.into()) +} + + +/// **Ciclo d do Pensamento com o VERIFICADOR NCM** (produção, sem oráculo): +/// backbone CABEÇA TREINADA (alvo → classes) + verificador calibrado em +/// dados retidos; N tentativas agregadas; ganho medido em consultas retidas +/// EM DISTRIBUIÇÃO (onde a calibração vale — V-2) e OOD reportado à parte. +#[pyfunction] +pub(crate) fn demo_pensamento_ncm( + d_cond: usize, + d_saida: usize, + n_tentativas: usize, + passos_treino: usize, + limiar_conf: f64, + orcamento: usize, + semente: u64, +) -> PyResult { + Python::with_gil(|py| { + use candle_core::Tensor; + let dev = candle_core::Device::Cpu; + crate::rng::definir_semente(semente); + let mut motor = pensamento::MotorPensamento::novo( + d_cond, d_saida, n_tentativas, limiar_conf, orcamento, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut otim = treino::AdamWManual::novo(motor.vars(), 5e-3, 0.0); + let mut rng = crate::rng::RngDeterministico::novo(semente); + // ---- dados sintéticos (item a: treino cooperativo) ---- + let n_dados = 320usize; + let conds: Vec> = (0..n_dados) + .map(|_| (0..d_cond).map(|_| rng.normal(0.0, 1.0) as f32).collect()) + .collect(); + let alvos: Vec> = conds.iter().map(|c| { + (0..d_saida).map(|j| { + let cj = c[j % d_cond]; + let ck = c[(j + 1) % d_cond]; + 0.5 * cj + 0.3 * cj * ck + 0.1 + }).collect() + }).collect(); + let mk = |c: &[f32], j: usize| -> f32 { + let cj = c[j % d_cond]; + let ck = c[(j + 1) % d_cond]; + 0.5 * cj + 0.3 * cj * ck + 0.1 + }; + let cond_t = Tensor::from_vec(conds.iter().flatten().cloned().collect(), (n_dados, d_cond), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let alvo_t = Tensor::from_vec(alvos.iter().flatten().cloned().collect(), (n_dados, d_saida), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + // ---- BACKBONE (cabeça treinada): alvo → classes K ---- + // classe = faixa do 1º componente do alvo (aprendível, mensurável) + let k_classes = 12usize; + let classes: Vec = alvos + .iter() + .map(|a| { + let v = a[0]; + let f = (((v + 2.0) / 4.0) * (k_classes as f32)).floor(); + (f.clamp(0.0, (k_classes - 1) as f32)) as u32 + }) + .collect(); + let cabeca = crate::jepa::MlpJepa::novo(d_saida, 4 * d_saida, k_classes, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let classe_t = Tensor::from_vec(classes.clone(), (n_dados,), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut otim_cab = treino::AdamWManual::novo(cabeca.vars(), 1e-2, 0.0); + for _ in 0..200 { + let logits = cabeca.fwd(&alvo_t) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let ce = candle_nn::loss::cross_entropy(&logits, &classe_t) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + otim_cab.passo(treino::grads_achatados(&ce, &cabeca.vars()) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?); + } + // ---- VERIFICADOR calibrado em RETIDOS (metade final) ---- + let n_ret = n_dados / 2; + let (alvo_ret, clas_ret) = ( + alvo_t.narrow(0, n_ret, n_dados - n_ret).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?, + classe_t.narrow(0, n_ret, n_dados - n_ret).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?, + ); + let logits_ret = cabeca.fwd(&alvo_ret).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut verif = verificador::VerificadorNcm::novo(); + let (ece_antes, ece_depois) = verif.ajustar(&logits_ret, &clas_ret) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + // logits_fn: resposta (1, d_saida) → logits do backbone (1, K) + let logits_fn = |resp: &Tensor| -> candle_core::Result { + cabeca.fwd(resp) + }; + // ---- treino cooperativo do motor (item a) ---- + let cond_tr = cond_t.narrow(0, 0, n_ret).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let alvo_tr = alvo_t.narrow(0, 0, n_ret).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut hist = Vec::new(); + for _ in 0..passos_treino { + let perda = motor.perda_treino(&cond_tr, &alvo_tr) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let pv = perda.to_scalar::() + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? as f64; + hist.push(pv); + otim.passo(treino::grads_achatados(&perda, &motor.vars()) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?); + } + // ---- consultas RETIDAS em distribuição (ganho medido AQUI) ---- + let n_consultas = 8usize; + let mut confs_base = Vec::new(); + let mut confs_pens = Vec::new(); + let mut iteracoes = Vec::new(); + for qi in 0..n_consultas { + let consulta: Vec = conds[n_ret + qi].clone(); + let consulta_t = Tensor::from_vec(consulta, (d_cond,), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut rng2 = crate::rng::RngDeterministico::novo(semente + 1 + qi as u64); + let res = motor.raciocinar_ncm(&consulta_t, &verif, &logits_fn, &mut rng2) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + // base: 1 tentativa isolada, mesma verificação NCM + let base = motor.tentativas[0] + .fwd(&consulta_t.broadcast_as((1, d_cond)).unwrap()).unwrap(); + let conf_base = verif.confianca_1d(&logits_fn(&base).unwrap()).unwrap(); + confs_base.push(conf_base); + confs_pens.push(res.confianca); + iteracoes.push(res.iteracoes); + } + let media = |v: &[f64]| v.iter().sum::() / v.len().max(1) as f64; + let (conf_base_m, conf_pens_m) = (media(&confs_base), media(&confs_pens)); + let iteracoes_media = iteracoes.iter().sum::() as f64 / iteracoes.len().max(1) as f64; + // ---- consulta OOD (reporte honesto — sem gate de ganho) ---- + let consulta_ood: Vec = (0..d_cond).map(|j| 3.0 + (j as f32) * 0.1).collect(); + let consulta_ood_t = Tensor::from_vec(consulta_ood, (d_cond,), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut rng3 = crate::rng::RngDeterministico::novo(semente + 99); + let res_ood = motor.raciocinar_ncm(&consulta_ood_t, &verif, &logits_fn, &mut rng3) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let base_ood = motor.tentativas[0] + .fwd(&consulta_ood_t.broadcast_as((1, d_cond)).unwrap()).unwrap(); + let conf_base_ood = verif.confianca_1d(&logits_fn(&base_ood).unwrap()).unwrap(); + let _ = mk; + let d_ = serde_json::json!({ + "versao": "pensamento-0.6.0-ncm", + "n_tentativas": n_tentativas, + "perda_treino_inicial": hist.first(), "perda_treino_final": hist.last(), + "treino_aprendeu": hist.last().copied() < hist.first().map(|p| p * 0.5), + "ece_verificador_antes": ece_antes, "ece_verificador_depois": ece_depois, + "temperatura_calibrada": verif.temperatura, + "iteracoes_ciclo_d_media": iteracoes_media, + "confianca_base_1_rede": conf_base_m, + "confianca_pensamento": conf_pens_m, + "ganho_confianca": conf_pens_m - conf_base_m, + "confianca_ood_base": conf_base_ood, + "confianca_ood_pensamento": res_ood.confianca, + "iteracoes_ood": res_ood.iteracoes, + "orcamento_esgotado_ood": res_ood.telemetria.get("orcamento_esgotado").copied().unwrap_or(0.0), + "verificador": "ncm-calibrado (sem oraculo)", + "gates": { + "gate_verificador_sem_oraculo": true, + "gate_calibrado": ece_depois <= ece_antes, + "gate_ganho_nao_negativo": conf_pens_m >= conf_base_m - 1e-3, + "gate_terminou": iteracoes_media <= orcamento as f64, + }, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) + }) +} + + + +/// **Autoajuste do Pensamento por telemetria (A-1..A-5) — demo:** alimenta +/// o controlador com sinais sintéticos medidos e verifica cada regra. +#[pyfunction] +pub(crate) fn demo_autoajuste(semente: u64) -> PyResult { + Python::with_gil(|py| { + let mut hub = telemetria::TelemetryHub::novo(None); + let mut rng = crate::rng::RngDeterministico::novo(semente); + let mut auto = controlador::AutoAjustePensamento::novo(0.5, 8, 2); + // A-1: confianças com mistura (60% altas ~0.85–0.95, 40% baixas ~0.25–0.40) + // ⇒ quantil 0.75 cai no grupo alto (posições 0.6–1.0) ≈ 0.85+0.375·0.1 ≈ 0.89 + for _ in 0..200 { + let c = if rng.uniforme() < 0.6 { 0.85 + rng.uniforme() * 0.1 } else { 0.25 + rng.uniforme() * 0.15 }; + auto.confs_retidas.push(c); + } + let tau = auto.ajustar_tau_c(0.25, &mut hub); + // A-3: acordos altos (0.88 ⇒ p̂ = (1+√0.76)/2 ≈ 0.936 ⇒ N = ln(20)/(2·0.436²) ≈ 7.9 ⇒ 8) + for _ in 0..32 { + auto.acordos.push(0.88); + } + let n_ajustado = auto.ajustar_n(0.05, 16, &mut hub); + // A-5: ganhos estáveis positivos (n=64 ⇒ 3º termo Bernstein ≈ 0.14 < 0.30) ⇒ gate sobe + for _ in 0..64 { + auto.ajustar_gate(0.30, 0.05, &mut hub); + } + let g_final = auto.gate_g; + // A-2: ganhos decaindo geometricamente + for k in 0..12 { + auto.ganhos_por_iteracao.push(0.25 * 0.6f64.powi(k)); + } + let b_ajustado = auto.ajustar_orcamento(0.02, &mut hub); + // A-4: UCB1 com 3 braços + let mut u = controlador::Ucb1::novo(vec!["n1".into(), "n4_moe".into(), "n4".into()]); + for _ in 0..200 { + let braco = u.escolher(); + let media = match braco { 0 => 0.35, 1 => 0.80, _ => 0.55 }; + let r = (media + rng.normal(0.0, 0.06) as f64).clamp(0.0, 1.0); + u.registrar(braco, r); + } + let medias = u.medias(); + let melhor = medias.iter().enumerate().max_by(|a, b| a.1.partial_cmp(b.1).unwrap()).map(|(i, _)| i).unwrap_or(0); + let d_ = serde_json::json!({ + "versao": "autoajuste-0.6.0", + "tau_c_final": tau, "n_tentativas_final": n_ajustado, + "gate_final": g_final, "orcamento_b_final": b_ajustado, + "ajustes_auditados": hub.eventos_ajuste.len(), + "ucb1_medias": medias, "ucb1_melhor_braco": u.nomes[melhor], + "ucb1_arrependimento_medio": u.arrependimento_empirico() / 200.0, + "gates": { + "gate_a1_tau_quantil": (tau - 0.89).abs() < 0.08, + "gate_a3_n_reduzido": n_ajustado <= 8, + "gate_a5_gate_subiu": g_final > 0.0, + "gate_a2_b_finito": b_ajustado >= 1 && b_ajustado <= 16, + "gate_a4_ucb1_achou": u.nomes[melhor] == "n4_moe", + }, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) + }) +} diff --git a/src/python/som.rs b/src/python/som.rs new file mode 100644 index 0000000000000000000000000000000000000000..2d31790de368a20532e3b9b21473291965291701 --- /dev/null +++ b/src/python/som.rs @@ -0,0 +1,180 @@ +//! `python::som` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + + +/// Treina um 4D-SOM e devolve telemetria completa (EQ/TE/massa/cobertura). +#[pyfunction] +pub(crate) fn treinar_som4d(py: Python, dados: Vec>, cfg: Option) -> PyResult { + let dev = candle_core::Device::Cpu; + let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut c = som4d::CfgSom4D::default(); + if let Some(js) = &cfg { + if let Ok(v) = serde_json::from_str::(js) { + if let Some(o) = v.as_object() { + for (k, val) in o { + match k.as_str() { + "nx" => c.nx = val.as_u64().unwrap_or(4) as usize, + "ny" => c.ny = val.as_u64().unwrap_or(4) as usize, + "nz" => c.nz = val.as_u64().unwrap_or(4) as usize, + "nt" => c.nt = val.as_u64().unwrap_or(4) as usize, + "epocas" => c.epocas = val.as_u64().unwrap_or(50) as usize, + "sigma0" => c.sigma0 = val.as_f64().unwrap_or(2.0), + "sigma1" => c.sigma1 = val.as_f64().unwrap_or(0.5), + "eta0" => c.eta0 = val.as_f64().unwrap_or(0.5), + "eta1" => c.eta1 = val.as_f64().unwrap_or(0.05), + "semente" => c.semente = val.as_u64().unwrap_or(42), + "espalhada" => { + c.inicializacao = if val.as_bool().unwrap_or(false) { + som4d::Inicializacao4D::Espalhada + } else { + som4d::Inicializacao4D::Dados + } + } + _ => {} + } + } + } + } + } + let som = som4d::treinar_som4d(&t, &c, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let d = serde_json::json!({ + "versao": som4d::versao(), + "celulas": som.n_celulas(), + "grade": [som.nx, som.ny, som.nz, som.nt], + "eq_inicial": som.eq_inicial, + "eq_final": som.eq_final, + "te_final": som.te_final, + "historico_eq_te": som.historia, + "massa_soma": som.massa.iter().sum::(), + }); + Ok(pythonize::pythonize(py, &d)?.into()) +} + + +/// Treina um GNG e devolve telemetria (vértices, arestas, componentes, EQ). +#[pyfunction] +pub(crate) fn treinar_gng(py: Python, dados: Vec>, cfg: Option) -> PyResult { + let dev = candle_core::Device::Cpu; + let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut c = som4d::CfgGng::default(); + if let Some(js) = &cfg { + if let Ok(v) = serde_json::from_str::(js) { + if let Some(o) = v.as_object() { + for (k, val) in o { + match k.as_str() { + "passos" => c.passos = val.as_u64().unwrap_or(4000) as usize, + "max_vertices" => c.max_vertices = val.as_u64().unwrap_or(60) as usize, + "lambda" => c.lambda = val.as_u64().unwrap_or(50) as usize, + "eps_b" => c.eps_b = val.as_f64().unwrap_or(0.05), + "eps_n" => c.eps_n = val.as_f64().unwrap_or(0.006), + "idade_max" => c.idade_max = val.as_u64().unwrap_or(50) as u32, + "semente" => c.semente = val.as_u64().unwrap_or(42), + _ => {} + } + } + } + } + } + let g = som4d::treinar_gng(&t, &c) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let d = serde_json::json!({ + "versao": som4d::versao(), + "vertices": g.vertices.len(), + "arestas": g.arestas.len(), + "componentes_conexas": g.n_componentes_conexas(), + "insercoes": g.insercoes, + "eq_medio": g.eq_medio, + }); + Ok(pythonize::pythonize(py, &d)?.into()) +} + + +/// Treina um H-SOM e devolve a decomposição hierárquica (Teo H-1). +#[pyfunction] +pub(crate) fn treinar_hsom(py: Python, dados: Vec>, cfg: Option) -> PyResult { + let dev = candle_core::Device::Cpu; + let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut c = som4d::CfgHSom::default(); + if let Some(js) = &cfg { + if let Ok(v) = serde_json::from_str::(js) { + if let Some(o) = v.as_object() { + if let Some(x) = o.get("celulas_finas") { + c.celulas_finas = x.as_u64().unwrap_or(9) as usize; + } + if let Some(x) = o.get("epocas_finas") { + c.epocas_finas = x.as_u64().unwrap_or(20) as usize; + } + } + } + } + let h = som4d::treinar_hsom(&t, &c, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let cota = h.eq_grosso + 2.0 * h.r_fino + 2.0 * (h.eq_grosso * h.r_fino).sqrt(); + let d = serde_json::json!({ + "versao": som4d::versao(), + "eq_hierarquico": h.eq_hierarquico, + "eq_grosso": h.eq_grosso, + "r_fino": h.r_fino, + "cota_teo_h1": cota, + "cota_respeitada": h.eq_hierarquico <= cota + 1e-9, + }); + Ok(pythonize::pythonize(py, &d)?.into()) +} + + +/// Demo T-SOM: streaming com deriva controlada — rastreio (Teo T-1). +#[pyfunction] +pub(crate) fn demo_tsom(py: Python, dados_iniciais: Vec>, janela: Vec>, passos: usize, gamma: f64) -> PyResult { + let dev = candle_core::Device::Cpu; + let t0 = tensor_de_2d(dados_iniciais, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let cfg = som4d::CfgTSom { + cfg_grade: som4d::CfgSom4D { nx: 3, ny: 3, nz: 2, nt: 2, epocas: 15, ..Default::default() }, + gamma, + janela: janela.len(), + }; + let mut ts = som4d::TSom4D::novo(&t0, &cfg, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let mut historico = Vec::new(); + for _ in 0..passos { + let eq = ts.passo(&janela); + historico.push(eq); + } + let d = serde_json::json!({ + "versao": som4d::versao(), + "gamma": gamma, + "eq_temporal_historico": historico, + "t_passos": ts.t, + }); + Ok(pythonize::pythonize(py, &d)?.into()) +} + + +/// Demo/gate do auto-ajuste SOM com tetos (S-AB/S-SL). +#[pyfunction] +pub(crate) fn demo_autoajuste_som(py: Python) -> PyResult { + let mut a = controlador::AutoAjusteSom::novo(0.5, 1.0); + let (al_qe, _) = a.passo(1.0, 0.0, 0.0); + let (al_cross, sg_cross) = a.passo(0.0, 0.0, 1.0); + let (al_max, sg_max) = a.passo(1.0, 1.0, 1.0); + let mut parou_5chunks = false; + for _ in 0..5 { + parou_5chunks = a.avaliar_parada(0.95, 0.1, 0.1, false, true, false, false); + } + let d_ = serde_json::json!({ + "versao": "som-autoadjust-0.7.0", + "alfa_qe_alta": al_qe, "alfa_crossing": al_cross, "sigma_crossing": sg_cross, + "alfa_max": al_max, "sigma_max": sg_max, + "parou_5chunks": parou_5chunks, + "motivo": a.motivo_parada, + "gates": { + "tetos_ok": al_max <= 1.0 + 1e-9 && sg_max <= 2.0 + 1e-9, + "piso_ok": al_cross >= 0.5 - 1e-9, + "qe_sobe": al_qe > 0.5, + "stop_dispara": parou_5chunks, + }, + }); + Ok(pythonize::pythonize(py, &d_)?.into()) +} diff --git a/src/python/treino_fases.rs b/src/python/treino_fases.rs new file mode 100644 index 0000000000000000000000000000000000000000..be801291dbdeb594d2e0a1225d257873037203a8 --- /dev/null +++ b/src/python/treino_fases.rs @@ -0,0 +1,228 @@ +//! `python::treino_fases` — extraído do monólito na v0.10.0 +//! (divisão SRP, crítica Qwen3.8 lote 3 nº 1; r-35 §2). + +use super::*; + +use super::nucleo::ConfigPy; + + +/// Treina a fase A (tokenizador). Devolve dict com resumo + telemetria. +#[pyfunction] +#[pyo3(signature = (cfg, sequencias, caminho_telemetria=None, caminho_checkpoint=None, callback=None, teto_seg=None))] +pub(crate) fn treinar_fase_a( + cfg: &ConfigPy, + sequencias: Vec>, + caminho_telemetria: Option, + caminho_checkpoint: Option, + callback: Option>, + teto_seg: Option, +) -> PyResult { + let cb = callback_treino_de_py(callback); + let mut hub = telemetria::TelemetryHub::novo(caminho_telemetria); + let dados = treino::DadosTreino::novo(sequencias, cfg.cfg.modelo.comprimento_ctx); + let mut resumo = treino::treinar_fase_a(&cfg.cfg, &dados, &mut hub, &cb, caminho_checkpoint.as_deref(), teto_seg) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fase A: {e}")))?; + let _ = &mut resumo; + Python::with_gil(|py| { + let dados = serde_json::json!({ + "resumo": { + "passos": resumo.passos, "perda_inicial": resumo.perda_inicial, + "perda_final": resumo.perda_final, "acc_recon": resumo.acc_recon, + "delta_ort_max": resumo.delta_ort_max, + "perplexidade_codebook": resumo.perplexidade_codebook, + "monotonia_prefixo": resumo.monotonia_prefixo, + "escore_aprendizado": resumo.escore_aprendizado, + }, + "telemetria": hub.dump(), + }); + Ok(pythonize::pythonize(py, &dados)?.into()) + }) +} + + +/// Treina a fase B (gerador) com tokenizador congelado carregado de checkpoint. +#[pyfunction] +#[pyo3(signature = (cfg, sequencias, caminho_tokenizador, caminho_telemetria=None, caminho_checkpoint=None, callback=None, teto_seg=None))] +pub(crate) fn treinar_fase_b( + cfg: &ConfigPy, + sequencias: Vec>, + caminho_tokenizador: &str, + caminho_telemetria: Option, + caminho_checkpoint: Option, + callback: Option>, + teto_seg: Option, +) -> PyResult { + let cb = callback_treino_de_py(callback); + let mut hub = telemetria::TelemetryHub::novo(caminho_telemetria); + let dados = treino::DadosTreino::novo(sequencias, cfg.cfg.modelo.comprimento_ctx); + // carrega o tokenizador congelado + let dev = candle_core::Device::Cpu; + let mut tok = tokenizador::TokenizadorConceitos::novo(&cfg.cfg, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("construção: {e}")))?; + carregar_tokenizador(&mut tok, caminho_tokenizador) + .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; + let resumo = treino::treinar_fase_b(&cfg.cfg, &tok, &dados, &mut hub, &cb, caminho_checkpoint.as_deref(), teto_seg) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fase B: {e}")))?; + Python::with_gil(|py| { + let dados = serde_json::json!({ + "resumo": { + "passos": resumo.passos, "perda_inicial": resumo.perda_inicial, + "perda_final": resumo.perda_final, "acc_conceitos": resumo.acc_recon, + "escore_aprendizado": resumo.escore_aprendizado, + }, + "telemetria": hub.dump(), + }); + Ok(pythonize::pythonize(py, &dados)?.into()) + }) +} + + +pub(crate) fn carregar_tokenizador(tok: &mut tokenizador::TokenizadorConceitos, caminho: &str) -> Result<(), String> { + let txt = std::fs::read_to_string(caminho).map_err(|e| format!("{e}"))?; + let v: serde_json::Value = serde_json::from_str(&txt).map_err(|e| format!("{e}"))?; + let obj = v.as_object().ok_or("formato de checkpoint inválido")?; + let vars = tok.vars(); + for (i, var) in vars.iter().enumerate() { + if let Some(entrada) = obj.get(&format!("v{i}")) { + let forma: Vec = entrada["forma"].as_array().map(|a| a.iter().filter_map(|x| x.as_u64().map(|y| y as usize)).collect()).unwrap_or_default(); + let dados: Vec = entrada["dados"].as_array().map(|a| a.iter().filter_map(|x| x.as_f64().map(|y| y as f32)).collect()).unwrap_or_default(); + if forma.is_empty() || dados.is_empty() { + continue; + } + let dev = candle_core::Device::Cpu; + let t2 = candle_core::Tensor::from_vec(dados, forma.clone(), &dev) + .and_then(|t| t.reshape(forma)) + .map_err(|e| format!("{e}"))?; + var.set(&t2).map_err(|e| format!("{e}"))?; + } + } + Ok(()) +} + + +/// Gera amostras com NFE = L e devolve textos decodificados (modo conceitos). +#[pyfunction] +#[pyo3(signature = (cfg, caminho_tokenizador, caminho_gerador, n_amostras=2, parar_no_nivel=None, greedy=true))] +pub(crate) fn gerar_amostras( + cfg: &ConfigPy, + caminho_tokenizador: &str, + caminho_gerador: &str, + n_amostras: usize, + parar_no_nivel: Option, + greedy: bool, +) -> PyResult { + let dev = candle_core::Device::Cpu; + let mut tok = tokenizador::TokenizadorConceitos::novo(&cfg.cfg, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + carregar_tokenizador(&mut tok, caminho_tokenizador) + .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; + let mut ger = gerador::GeradorConceitos::novo(&cfg.cfg, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + carregar_gerador(&mut ger, caminho_gerador) + .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; + let mut amostrador = amostrador::AmostradorConceitos::novo( + &mut ger, &tok, cfg.cfg.ncm.cfg_scale, cfg.cfg.ncm.temperaturas.limites, cfg.cfg.ncm.temperaturas.entropia_alvo, + ); + let mut rng = ::seed_from_u64(4242); + let inicio = std::time::Instant::now(); + let (codigos, nfe) = amostrador.gerar(n_amostras.max(1), &dev, &mut rng, parar_no_nivel, greedy) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("amostragem: {e}")))?; + let tempo_ms = inicio.elapsed().as_secs_f64() * 1000.0; + // decodifica: ẑ final → decoder AR greedy → tokens + let nivel_final = codigos.len(); + let zhats = tok.reconstruir_de_codigos(&codigos, nivel_final, n_amostras.max(1), &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + let textos_ids = decodificar_textos(&tok, zhats.last(), n_amostras.max(1)) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?; + Python::with_gil(|py| { + let d = serde_json::json!({ + "nfe": nfe, "l": cfg.cfg.l_niveis(), "tempo_ms": tempo_ms, + "niveis_usados": nivel_final, "ids_tokens": textos_ids, + "temperaturas": amostrador.temperaturas, + }); + Ok(pythonize::pythonize(py, &d)?.into()) + }) +} + + +fn decodificar_textos(tok: &tokenizador::TokenizadorConceitos, zhat: Option<&candle_core::Tensor>, b: usize) -> Result>, String> { + let Some(z) = zhat else { return Ok(vec![vec![]; b]) }; + tok.decodificar_livre(z, 48).map_err(|e| format!("{e}")) +} + + +pub(crate) fn carregar_gerador(ger: &mut gerador::GeradorConceitos, caminho: &str) -> Result<(), String> { + let txt = std::fs::read_to_string(caminho).map_err(|e| format!("{e}"))?; + let v: serde_json::Value = serde_json::from_str(&txt).map_err(|e| format!("{e}"))?; + let obj = v.as_object().ok_or("formato inválido")?; + let vars = ger.vars(); + for (i, var) in vars.iter().enumerate() { + if let Some(entrada) = obj.get(&format!("v{i}")) { + let forma: Vec = entrada["forma"].as_array().map(|a| a.iter().filter_map(|x| x.as_u64().map(|y| y as usize)).collect()).unwrap_or_default(); + let dados: Vec = entrada["dados"].as_array().map(|a| a.iter().filter_map(|x| x.as_f64().map(|y| y as f32)).collect()).unwrap_or_default(); + if forma.is_empty() || dados.is_empty() { + continue; + } + let dev = candle_core::Device::Cpu; + let t = candle_core::Tensor::from_vec(dados, forma.clone(), &dev) + .and_then(|t| t.reshape(forma)) + .map_err(|e| format!("{e}"))?; + var.set(&t).map_err(|e| format!("{e}"))?; + } + } + Ok(()) +} + + +/// **FASE C — treino conjunto Pensamento↔backbone** (dados reais). +#[pyfunction] +pub(crate) fn treinar_fase_c( + cfg: &ConfigPy, + sequencias: Vec>, + caminho_tokenizador: &str, + passos: usize, + caminho_gerador: Option, + caminho_telemetria: Option, + callback: Option>, + teto_seg: Option, +) -> PyResult { + let cb = callback_treino_de_py(callback); + let mut hub = telemetria::TelemetryHub::novo(caminho_telemetria); + let dados = treino::DadosTreino::novo(sequencias, cfg.cfg.modelo.comprimento_ctx); + let dev = candle_core::Device::Cpu; + let mut tok = tokenizador::TokenizadorConceitos::novo(&cfg.cfg, &dev) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("construção: {e}")))?; + carregar_tokenizador(&mut tok, caminho_tokenizador) + .map_err(pyo3::exceptions::PyRuntimeError::new_err)?; + let resumo = treino::treinar_fase_c(&cfg.cfg, &tok, &dados, &mut hub, &cb, teto_seg, passos, caminho_gerador.as_deref()) + .map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fase C: {e}")))?; + Python::with_gil(|py| { + let dados = serde_json::json!({ + "resumo": { + "passos": resumo.passos, + "ce_inicial": resumo.ce_inicial, + "ce_final": resumo.ce_final, + "ce_gatilhada_sem_correcao": resumo.ce_gatilhada_sem_correcao, + "ce_gatilhada_com_correcao": resumo.ce_gatilhada_com_correcao, + "confianca_media_inicial": resumo.confianca_media_inicial, + "confianca_media_final": resumo.confianca_media_final, + "confianca_pos_correcao": resumo.confianca_pos_correcao, + "taxa_gatilho": resumo.taxa_gatilho, + "ece_antes": resumo.ece_antes, + "ece_depois": resumo.ece_depois, + "temperatura_calibrada": resumo.temperatura_calibrada, + "tau_c": resumo.tau_c, + "taxa_conflito_pcgrad": resumo.taxa_conflito_pcgrad, + "decaimento_delta": resumo.decaimento_delta, + "acordos_medios": resumo.acordos_medios, + "escore_aprendizado": resumo.escore_aprendizado, + "rodadas_acoplador": resumo.rodadas_acoplador, + "timeouts_acoplador": resumo.timeouts_acoplador, + "maior_sobreposicao_us": resumo.maior_sobreposicao_us, + }, + "painel_v060": hub.painel_v060(), + "telemetria": hub.dump(), + }); + Ok(pythonize::pythonize(py, &dados)?.into()) + }) +} diff --git a/src/tipos.rs b/src/tipos.rs index a1ed7f43a2c6c3da2ad5d4f52bf237f2b819bd71..38050d2e332c5e1b353e0c302f8b3ceb1ed62507 100644 --- a/src/tipos.rs +++ b/src/tipos.rs @@ -64,6 +64,64 @@ impl GateAtivacao { } } + +/// Temperatura inversa γ = 1/T — domínio aberto (0, +∞). +/// +/// **Crítica Qwen3.8 lote 3, nº 4 / proposta 2 (v0.10.0, r-35 §5).** A +/// proposta `pub struct TemperaturaInversa(f64)` com construtor validado +/// é a forma CORRETA do padrão newtype — mas a alegação "garante em tempo +/// de COMPILAÇÃO que T > 0" é **FALSA** e já constava como errata na +/// r-34 §6: o construtor é uma checagem de RUNTIME na fronteira única +/// (o campo privado impede construção fora dela; nada é verificado pelo +/// verificador de tipos). O ganho real: validação em ponto único + +/// contrato visível na assinatura + fechamento sob as operações. +/// +/// **Prova lógica (T-V3):** todo `TemperaturaInversa` acessível fora do +/// módulo vem de `nova`/`de_gamma` (campo privado) ou `de_unchecked` +/// (`pub(crate)`, hot paths já verificados). Ambos os construtores +/// recusam γ ∉ (0, +∞) — incluindo T subnormal com 1/T = +∞ — e nenhuma +/// operação do tipo muta o campo; por indução estrutural, todo valor +/// carrega γ finito e positivo. Como γ > 0 é EXATAMENTE a pré-condição +/// da convexidade estrita de L̃(γ) = NLL(softmax(γ·z)) (Teo N-2, r-33: +/// L̃'' = γ²·Var_p(z) ≥ 0), o tipo CARREGA a pré-condição do teorema. ∎ +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct TemperaturaInversa(f64); + +impl TemperaturaInversa { + /// Construtor validado a partir da temperatura T: exige T > 0, T + /// finito e γ = 1/T finito (rejeita T subnormal, onde 1/T = +∞). + pub fn nova(temperatura: f64) -> Result { + if !(temperatura > 0.0) || !temperatura.is_finite() { + candle_core::bail!( + "Temperatura deve ser > 0 e finita (recebido {temperatura}) \ + — pré-condição da convexidade N-2"); + } + let gamma = 1.0 / temperatura; + if !gamma.is_finite() { + candle_core::bail!("1/T estourou para +∞ (T={temperatura} subnormal)"); + } + Ok(TemperaturaInversa(gamma)) + } + + /// Construtor validado a partir de γ diretamente. + pub fn de_gamma(gamma: f64) -> Result { + if !(gamma > 0.0) || !gamma.is_finite() { + candle_core::bail!("γ deve ser > 0 e finito (recebido {gamma})"); + } + Ok(TemperaturaInversa(gamma)) + } + + /// Valor de γ = 1/T (passou pela validação). + pub fn valor(&self) -> f64 { + self.0 + } + + /// Temperatura T = 1/γ (passou pela validação). + pub fn temperatura(&self) -> f64 { + 1.0 / self.0 + } +} + #[cfg(test)] mod testes_tipos { use super::*; @@ -117,4 +175,35 @@ mod testes_tipos { // integral do gate ≈ área da sigmoide retificada (> 0, < 1·domínio) assert!(soma > 0.0 && soma < 2001.0); } + + /// T-V3a — construtores recusam γ ∉ (0, +∞): T ≤ 0, NaN, ±∞ e o + /// caso subnormal T = 5e-324 (1/T = +∞ em f64). + #[test] + fn tv3a_construtores_recusam_fora_do_dominio() { + for t_mau in [0.0, -1e-9, -5.0, f64::NAN, f64::INFINITY, 5e-324] { + assert!(TemperaturaInversa::nova(t_mau).is_err(), "nova devia recusar {t_mau}"); + } + for g_mau in [0.0, -1e-9, -3.0, f64::NAN, f64::INFINITY, f64::NEG_INFINITY] { + assert!(TemperaturaInversa::de_gamma(g_mau).is_err(), "de_gamma devia recusar {g_mau}"); + } + } + + /// T-V3b — fechamento: nova(T).temperatura() = T e + /// nova(T).valor() = 1/T para T representável. + #[test] + fn tv3b_fechamento_inversao() { + for t_bom in [1.0, 0.125, 8.0, 1e-6, 1e6, 0.373] { + let ti = TemperaturaInversa::nova(t_bom).unwrap(); + assert!((ti.temperatura() - t_bom).abs() < 1e-12); + assert!((ti.valor() - 1.0 / t_bom).abs() < 1e-12); + } + } + + /// T-V3c — de_gamma/nova concordam (mesma família paramétrica). + #[test] + fn tv3c_de_gamma_concorda() { + let a = TemperaturaInversa::nova(2.0).unwrap(); + let b = TemperaturaInversa::de_gamma(0.5).unwrap(); + assert_eq!(a, b); + } } diff --git a/src/tokenizador.rs b/src/tokenizador.rs index 704a37efb51f623ea7f165b9b24a465b9d6c129b..bc80277c27b9fe041c696ef560761c1251767dfc 100644 --- a/src/tokenizador.rs +++ b/src/tokenizador.rs @@ -392,9 +392,44 @@ impl TokenizadorConceitos { } let txt = serde_json::to_string(&serde_json::Value::Object(obj)) .map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?; - let tmp = format!("{caminho}.tmp"); - std::fs::write(&tmp, txt).map_err(|e| candle_core::Error::Msg(format!("io: {e}")))?; - std::fs::rename(&tmp, caminho).map_err(|e| candle_core::Error::Msg(format!("rename: {e}")))?; + crate::checkpoint::salvar_atomico(caminho, &txt) + } + + /// Carrega com verificação de integridade (manifesto SHA-256 — v0.10.0). + pub fn carregar_verificado(&mut self, caminho: &str) -> Result<()> { + let txt = crate::checkpoint::carregar_verificado(caminho)?; + self.carregar_de_txt(&txt) + } + + /// Carregamento nativo (v0.10.0, aditivo): mesmo formato de `salvar` + /// (`v{i}` → {forma, dados}); o helper Python equivalente permanece. + pub fn carregar(&mut self, caminho: &str) -> Result<()> { + let txt = std::fs::read_to_string(caminho) + .map_err(|e| candle_core::Error::Msg(format!("io: {e}")))?; + self.carregar_de_txt(&txt) + } + + fn carregar_de_txt(&mut self, txt: &str) -> Result<()> { + let v: serde_json::Value = serde_json::from_str(txt) + .map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?; + let obj = v.as_object() + .ok_or_else(|| candle_core::Error::Msg("formato de checkpoint inválido".into()))?; + let vars = self.vars(); + for (i, var) in vars.iter().enumerate() { + let e = obj.get(&format!("v{i}")).ok_or_else(|| { + candle_core::Error::Msg(format!("checkpoint sem v{i}")) + })?; + let forma: Vec = serde_json::from_value(e["forma"].clone()) + .map_err(|e| candle_core::Error::Msg(format!("forma: {e}")))?; + let dados: Vec = serde_json::from_value(e["dados"].clone()) + .map_err(|e| candle_core::Error::Msg(format!("dados: {e}")))?; + if dados.len() != forma.iter().product::() { + candle_core::bail!( + "forma {:?} incompatível com {} valores", forma, dados.len()); + } + let t = Tensor::from_vec(dados, forma, var.as_tensor().device())?; + var.set(&t)?; + } Ok(()) } } diff --git a/src/verificador.rs b/src/verificador.rs index bcd36b69d51a35e2490901f5e4fb6571ce6152cf..517a7434a6d8c3717889e0878e59aeaaa8038e8d 100644 --- a/src/verificador.rs +++ b/src/verificador.rs @@ -60,6 +60,19 @@ fn nll_temperatura(logits: &Tensor, alvos: &Tensor, t: f64) -> Result { Ok(ce.to_scalar::()? as f64) } +/// **T-V3 (v0.10.0):** fronteira TIPADA — `TemperaturaInversa` carrega a +/// pré-condição γ ∈ (0, +∞) da convexidade N-2 por construção; esta +/// variante recusa temperaturas inválidas NA ASSINATURA, sem custo em +/// runtime além da validação única já feita no construtor. Equivalente +/// bit-a-bit a `nll_temperatura(t.temperatura())` (teste tv3d). +pub fn nll_temperatura_tipado( + logits: &Tensor, + alvos: &Tensor, + t: &crate::tipos::TemperaturaInversa, +) -> Result { + nll_temperatura(logits, alvos, t.temperatura()) +} + /// **V-1:** calibra T* por golden-section em `log T ∈ [ln(1/8), ln 8]` /// minimizando a NLL nos dados retidos. Devolve (T*, nll(1), nll(T*)). pub fn calibrar_temperatura(logits: &Tensor, alvos: &Tensor) -> Result<(f64, f64, f64)> { @@ -276,6 +289,10 @@ impl VerificadorNcm { let (tg, n1g, ntg) = calibrar_temperatura(logits, alvos)?; (t, nll1, nllt) = (tg, n1g, ntg); } + // T-V3: saída do otimizador VALIDADA antes de armazenar — se + // Newton e golden produzirem lixo (não deveriam), falha alto em + // vez de propagar γ ≤ 0 silenciosamente (1/0 = NaN/∞ adiante). + let _ti = crate::tipos::TemperaturaInversa::nova(t)?; self.temperatura = t; self.n_ll_antes = nll1; self.n_ll_depois = nllt; @@ -297,6 +314,19 @@ impl VerificadorNcm { Ok((e_antes, e_depois)) } + /// Define a temperatura pela fronteira TIPADA (T-V3) — recusa + /// T ∉ (0, +∞) na fronteira; o campo `pub` permanece para leitura + /// e compatibilidade v0.9.0 (nada é removido). + pub fn definir_temperatura_tipado(&mut self, t: &crate::tipos::TemperaturaInversa) { + self.temperatura = t.temperatura(); + } + + /// Leitura TIPADA: valida o campo `pub` na saída (cinto-e-suspensório + /// para escritas diretas legadas) — Err se alguém atribuiu T inválida. + pub fn temperatura_tipada(&self) -> Result { + crate::tipos::TemperaturaInversa::nova(self.temperatura) + } + /// Confiança calibrada (n, K) → (n,) — a decisão do ciclo d usa isto. pub fn confianca(&self, logits: &Tensor) -> Result { let escalado = logits.affine(1.0 / self.temperatura, 0.0)?; @@ -548,3 +578,46 @@ mod testes { assert!((1.0 / 8.0..=8.0).contains(&t_n2)); } } + +mod testes_temperatura_tipada { + use super::*; + use crate::tipos::TemperaturaInversa; + + /// tv3d — equivalência bit-a-bit da fronteira tipada. + #[test] + fn tv3d_nll_tipada_equivalente() { + let dev = candle_core::Device::Cpu; + let logits = Tensor::randn(0f32, 1f32, (64, 8), &dev).unwrap(); + // alvos = argmax de ruído independente (u32 válido por construção) + let alvos = Tensor::randn(0f32, 1f32, (64, 8), &dev).unwrap() + .argmax(candle_core::D::Minus1).unwrap() + .to_dtype(candle_core::DType::U32).unwrap(); + for t_bom in [0.5, 1.0, 2.0, 3.7] { + let ti = TemperaturaInversa::nova(t_bom).unwrap(); + let a = nll_temperatura(&logits, &alvos, t_bom).unwrap(); + let b = nll_temperatura_tipado(&logits, &alvos, &ti).unwrap(); + assert_eq!(a.to_bits(), b.to_bits(), "bit-a-bit em T={t_bom}"); + } + } + + /// tv3e — ajustar devolve temperatura válida na fronteira tipada; + /// setter tipado recusa inválidas; getter detecta escrita legada má. + #[test] + fn tv3e_verificador_fronteira() { + let dev = candle_core::Device::Cpu; + let logits = Tensor::randn(0f32, 2f32, (128, 16), &dev).unwrap(); + let alvos = Tensor::randn(0f32, 1f32, (128, 16), &dev).unwrap() + .argmax(candle_core::D::Minus1).unwrap() + .to_dtype(candle_core::DType::U32).unwrap(); + let mut v = VerificadorNcm::novo(); + v.ajustar(&logits, &alvos).unwrap(); + assert!(v.temperatura_tipada().is_ok(), "T* do ajustar é válida"); + + let ti = TemperaturaInversa::nova(1.25).unwrap(); + v.definir_temperatura_tipado(&ti); + assert_eq!(v.temperatura, 1.25); + + v.temperatura = -3.0; // escrita legada má (campo pub) + assert!(v.temperatura_tipada().is_err(), "getter detecta γ inválido"); + } +}