v0.5.0: suíte SOM-4D + suíte JEPA (12) + tecnologia Pensamento + telemetria Theil–Sen — 29/29 gates verdes (provas r-18..r-22)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- Cargo.lock +103 -1
- Cargo.toml +6 -7
- README.md +42 -44
- configs/base_rust.json +33 -22
- dados/bpe.json +0 -0
- docs/memorias/r-18-analise-md-externos-v050.md +131 -0
- docs/memorias/r-19-provas-som-v050.md +326 -0
- docs/memorias/r-20-provas-jepa-v050.md +344 -0
- docs/memorias/r-21-provas-pensamento-v050.md +266 -0
- docs/memorias/r-22-resultados-telemetria-v050.md +194 -0
- docs/referencias/E_ponte_credito_hardware.md +572 -0
- docs/referencias/F_mistura_verificador.md +556 -0
- docs/referencias/G_ssd_delta.md +609 -0
- docs/referencias/I_som_variantes.md +622 -0
- docs/referencias/r-16-provas-corrigidas-v040.md +355 -0
- docs/referencias/r-17-relatorio-v040.md +425 -0
- pyproject.toml +2 -9
- resultados/pensamento_v050.json +210 -0
- resultados/resumo_treino.json +27 -13
- resultados/suite_jepa_v050.json +380 -0
- resultados/suite_som_v050.json +79 -0
- resultados/suite_v.json +40 -86
- resultados/suite_v050_final.json +86 -0
- resultados/telemetria_fase_a.jsonl +0 -0
- resultados/telemetria_fase_b.jsonl +0 -0
- resultados/telemetria_v050.json +74 -0
- resultados/treino_v050.json +133 -0
- scripts/39_coletar_dados.py +20 -50
- scripts/40_treinar.py +24 -77
- scripts/41_teste.py +35 -214
- scripts/42_publicar.py +56 -139
- scripts/48_suite_som.py +114 -0
- scripts/49_suite_jepa.py +236 -0
- scripts/50_pensamento.py +112 -0
- scripts/51_treino_v050.py +181 -0
- scripts/52_telemetria_v050.py +107 -0
- scripts/53_suite_v050.py +112 -0
- scripts/54_publicar_v050.py +89 -0
- src/amostrador.rs +30 -135
- src/atencao.rs +46 -144
- src/camadas.rs +3 -5
- src/config.rs +10 -106
- src/controlador.rs +26 -104
- src/diagnostico.rs +23 -23
- src/gerador.rs +24 -149
- src/jepa.rs +1489 -0
- src/lib.rs +643 -458
- src/mascaras.rs +11 -131
- src/memoria.rs +1 -20
- src/pensamento.rs +645 -0
Cargo.lock
CHANGED
|
@@ -31,6 +31,12 @@ version = "0.2.21"
|
|
| 31 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 32 |
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
| 33 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
[[package]]
|
| 35 |
name = "arc-swap"
|
| 36 |
version = "1.9.3"
|
|
@@ -296,6 +302,17 @@ dependencies = [
|
|
| 296 |
"syn 2.0.119",
|
| 297 |
]
|
| 298 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
[[package]]
|
| 300 |
name = "dyn-stack"
|
| 301 |
version = "0.13.2"
|
|
@@ -717,13 +734,16 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
| 717 |
|
| 718 |
[[package]]
|
| 719 |
name = "khtst-rust"
|
| 720 |
-
version = "0.
|
| 721 |
dependencies = [
|
|
|
|
| 722 |
"built",
|
| 723 |
"candle-core",
|
| 724 |
"candle-nn",
|
| 725 |
"crossbeam-channel",
|
|
|
|
| 726 |
"log",
|
|
|
|
| 727 |
"pyo3",
|
| 728 |
"pyo3-async-runtimes",
|
| 729 |
"pyo3-built",
|
|
@@ -733,6 +753,7 @@ dependencies = [
|
|
| 733 |
"rayon",
|
| 734 |
"serde",
|
| 735 |
"serde_json",
|
|
|
|
| 736 |
"tokio",
|
| 737 |
]
|
| 738 |
|
|
@@ -776,6 +797,16 @@ version = "0.2.3"
|
|
| 776 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 777 |
checksum = "fc04a4c58212d57930a24bf47d3fa87485264a3a054e9c10e042eb373573ad3c"
|
| 778 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 779 |
[[package]]
|
| 780 |
name = "memchr"
|
| 781 |
version = "2.8.3"
|
|
@@ -829,6 +860,21 @@ dependencies = [
|
|
| 829 |
"syn 2.0.119",
|
| 830 |
]
|
| 831 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 832 |
[[package]]
|
| 833 |
name = "nom"
|
| 834 |
version = "7.1.3"
|
|
@@ -849,6 +895,15 @@ dependencies = [
|
|
| 849 |
"num-traits",
|
| 850 |
]
|
| 851 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 852 |
[[package]]
|
| 853 |
name = "num-traits"
|
| 854 |
version = "0.2.19"
|
|
@@ -869,6 +924,21 @@ dependencies = [
|
|
| 869 |
"libc",
|
| 870 |
]
|
| 871 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 872 |
[[package]]
|
| 873 |
name = "once_cell"
|
| 874 |
version = "1.21.4"
|
|
@@ -927,6 +997,15 @@ version = "1.15.0"
|
|
| 927 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 928 |
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
| 929 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 930 |
[[package]]
|
| 931 |
name = "ppv-lite86"
|
| 932 |
version = "0.2.21"
|
|
@@ -1170,6 +1249,12 @@ dependencies = [
|
|
| 1170 |
"bitflags",
|
| 1171 |
]
|
| 1172 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1173 |
[[package]]
|
| 1174 |
name = "rayon"
|
| 1175 |
version = "1.12.0"
|
|
@@ -1236,6 +1321,12 @@ version = "0.8.11"
|
|
| 1236 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1237 |
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
| 1238 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1239 |
[[package]]
|
| 1240 |
name = "rustix"
|
| 1241 |
version = "1.1.5"
|
|
@@ -1380,6 +1471,17 @@ version = "0.11.1"
|
|
| 1380 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1381 |
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
| 1382 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1383 |
[[package]]
|
| 1384 |
name = "syn"
|
| 1385 |
version = "2.0.119"
|
|
|
|
| 31 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 32 |
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
| 33 |
|
| 34 |
+
[[package]]
|
| 35 |
+
name = "anyhow"
|
| 36 |
+
version = "1.0.104"
|
| 37 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 38 |
+
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
| 39 |
+
|
| 40 |
[[package]]
|
| 41 |
name = "arc-swap"
|
| 42 |
version = "1.9.3"
|
|
|
|
| 302 |
"syn 2.0.119",
|
| 303 |
]
|
| 304 |
|
| 305 |
+
[[package]]
|
| 306 |
+
name = "dict_derive"
|
| 307 |
+
version = "0.5.1"
|
| 308 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 309 |
+
checksum = "846a520b3b7937dcfe564d9674d2d26da9e3cd0f8f1bbef51ca42fd6b7bb4f4a"
|
| 310 |
+
dependencies = [
|
| 311 |
+
"proc-macro2",
|
| 312 |
+
"quote",
|
| 313 |
+
"syn 1.0.109",
|
| 314 |
+
]
|
| 315 |
+
|
| 316 |
[[package]]
|
| 317 |
name = "dyn-stack"
|
| 318 |
version = "0.13.2"
|
|
|
|
| 734 |
|
| 735 |
[[package]]
|
| 736 |
name = "khtst-rust"
|
| 737 |
+
version = "0.5.0"
|
| 738 |
dependencies = [
|
| 739 |
+
"anyhow",
|
| 740 |
"built",
|
| 741 |
"candle-core",
|
| 742 |
"candle-nn",
|
| 743 |
"crossbeam-channel",
|
| 744 |
+
"dict_derive",
|
| 745 |
"log",
|
| 746 |
+
"numpy",
|
| 747 |
"pyo3",
|
| 748 |
"pyo3-async-runtimes",
|
| 749 |
"pyo3-built",
|
|
|
|
| 753 |
"rayon",
|
| 754 |
"serde",
|
| 755 |
"serde_json",
|
| 756 |
+
"thiserror 2.0.21",
|
| 757 |
"tokio",
|
| 758 |
]
|
| 759 |
|
|
|
|
| 797 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 798 |
checksum = "fc04a4c58212d57930a24bf47d3fa87485264a3a054e9c10e042eb373573ad3c"
|
| 799 |
|
| 800 |
+
[[package]]
|
| 801 |
+
name = "matrixmultiply"
|
| 802 |
+
version = "0.3.11"
|
| 803 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 804 |
+
checksum = "3f607c237553f086e7043417a51df26b2eb899d3caff94e6a67592ff992fedc7"
|
| 805 |
+
dependencies = [
|
| 806 |
+
"autocfg",
|
| 807 |
+
"rawpointer",
|
| 808 |
+
]
|
| 809 |
+
|
| 810 |
[[package]]
|
| 811 |
name = "memchr"
|
| 812 |
version = "2.8.3"
|
|
|
|
| 860 |
"syn 2.0.119",
|
| 861 |
]
|
| 862 |
|
| 863 |
+
[[package]]
|
| 864 |
+
name = "ndarray"
|
| 865 |
+
version = "0.16.1"
|
| 866 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 867 |
+
checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
|
| 868 |
+
dependencies = [
|
| 869 |
+
"matrixmultiply",
|
| 870 |
+
"num-complex",
|
| 871 |
+
"num-integer",
|
| 872 |
+
"num-traits",
|
| 873 |
+
"portable-atomic",
|
| 874 |
+
"portable-atomic-util",
|
| 875 |
+
"rawpointer",
|
| 876 |
+
]
|
| 877 |
+
|
| 878 |
[[package]]
|
| 879 |
name = "nom"
|
| 880 |
version = "7.1.3"
|
|
|
|
| 895 |
"num-traits",
|
| 896 |
]
|
| 897 |
|
| 898 |
+
[[package]]
|
| 899 |
+
name = "num-integer"
|
| 900 |
+
version = "0.1.47"
|
| 901 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 902 |
+
checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b"
|
| 903 |
+
dependencies = [
|
| 904 |
+
"num-traits",
|
| 905 |
+
]
|
| 906 |
+
|
| 907 |
[[package]]
|
| 908 |
name = "num-traits"
|
| 909 |
version = "0.2.19"
|
|
|
|
| 924 |
"libc",
|
| 925 |
]
|
| 926 |
|
| 927 |
+
[[package]]
|
| 928 |
+
name = "numpy"
|
| 929 |
+
version = "0.23.0"
|
| 930 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 931 |
+
checksum = "b94caae805f998a07d33af06e6a3891e38556051b8045c615470a71590e13e78"
|
| 932 |
+
dependencies = [
|
| 933 |
+
"libc",
|
| 934 |
+
"ndarray",
|
| 935 |
+
"num-complex",
|
| 936 |
+
"num-integer",
|
| 937 |
+
"num-traits",
|
| 938 |
+
"pyo3",
|
| 939 |
+
"rustc-hash",
|
| 940 |
+
]
|
| 941 |
+
|
| 942 |
[[package]]
|
| 943 |
name = "once_cell"
|
| 944 |
version = "1.21.4"
|
|
|
|
| 997 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 998 |
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
| 999 |
|
| 1000 |
+
[[package]]
|
| 1001 |
+
name = "portable-atomic-util"
|
| 1002 |
+
version = "0.2.8"
|
| 1003 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1004 |
+
checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715"
|
| 1005 |
+
dependencies = [
|
| 1006 |
+
"portable-atomic",
|
| 1007 |
+
]
|
| 1008 |
+
|
| 1009 |
[[package]]
|
| 1010 |
name = "ppv-lite86"
|
| 1011 |
version = "0.2.21"
|
|
|
|
| 1249 |
"bitflags",
|
| 1250 |
]
|
| 1251 |
|
| 1252 |
+
[[package]]
|
| 1253 |
+
name = "rawpointer"
|
| 1254 |
+
version = "0.2.1"
|
| 1255 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1256 |
+
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
|
| 1257 |
+
|
| 1258 |
[[package]]
|
| 1259 |
name = "rayon"
|
| 1260 |
version = "1.12.0"
|
|
|
|
| 1321 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1322 |
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
| 1323 |
|
| 1324 |
+
[[package]]
|
| 1325 |
+
name = "rustc-hash"
|
| 1326 |
+
version = "2.1.3"
|
| 1327 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1328 |
+
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
|
| 1329 |
+
|
| 1330 |
[[package]]
|
| 1331 |
name = "rustix"
|
| 1332 |
version = "1.1.5"
|
|
|
|
| 1471 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1472 |
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
| 1473 |
|
| 1474 |
+
[[package]]
|
| 1475 |
+
name = "syn"
|
| 1476 |
+
version = "1.0.109"
|
| 1477 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1478 |
+
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
| 1479 |
+
dependencies = [
|
| 1480 |
+
"proc-macro2",
|
| 1481 |
+
"quote",
|
| 1482 |
+
"unicode-ident",
|
| 1483 |
+
]
|
| 1484 |
+
|
| 1485 |
[[package]]
|
| 1486 |
name = "syn"
|
| 1487 |
version = "2.0.119"
|
Cargo.toml
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
[package]
|
| 2 |
name = "khtst-rust"
|
| 3 |
-
version = "0.
|
| 4 |
edition = "2021"
|
| 5 |
-
description = "KHTST_RUST — refatoração Rust/PyO3 do KHTST com NCM (Next-Concept Modeling), provas integradas, ADBA
|
| 6 |
license = "Apache-2.0"
|
| 7 |
|
| 8 |
[lib]
|
|
@@ -11,24 +11,23 @@ crate-type = ["cdylib", "rlib"]
|
|
| 11 |
|
| 12 |
[dependencies]
|
| 13 |
pyo3 = { version = "0.23", features = ["abi3-py310"] }
|
|
|
|
| 14 |
pythonize = "0.23"
|
| 15 |
pyo3-log = "0.12"
|
|
|
|
| 16 |
pyo3-built = "0.5"
|
| 17 |
pyo3-async-runtimes = { version = "0.23", optional = true, features = ["tokio-runtime"] }
|
| 18 |
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
| 19 |
candle-core = "0.11"
|
| 20 |
candle-nn = "0.11"
|
| 21 |
crossbeam-channel = "0.5"
|
| 22 |
-
# rayon: paralelismo real (scan SSD por lote, AdamW por variável — A35/M17).
|
| 23 |
rayon = "1"
|
| 24 |
serde = { version = "1", features = ["derive"] }
|
| 25 |
serde_json = "1"
|
|
|
|
|
|
|
| 26 |
log = "0.4"
|
| 27 |
rand = "0.8"
|
| 28 |
-
# v0.3.0 (A14/decisão do usuário): REMOVIDAS as dependências não usadas —
|
| 29 |
-
# `numpy` (rust-numpy), `dict_derive`, `anyhow`, `thiserror`. O binding de
|
| 30 |
-
# sequências permanece Vec<Vec<u32>> (2,4 MB para 2 394×256 — cópia
|
| 31 |
-
# desprezível; rust-numpy seria peso morto aqui).
|
| 32 |
|
| 33 |
[build-dependencies]
|
| 34 |
built = "0.8"
|
|
|
|
| 1 |
[package]
|
| 2 |
name = "khtst-rust"
|
| 3 |
+
version = "0.5.0"
|
| 4 |
edition = "2021"
|
| 5 |
+
description = "KHTST_RUST — refatoração Rust/PyO3 do KHTST com NCM (Next-Concept Modeling), provas integradas, atenção ADBA e ponte duplex"
|
| 6 |
license = "Apache-2.0"
|
| 7 |
|
| 8 |
[lib]
|
|
|
|
| 11 |
|
| 12 |
[dependencies]
|
| 13 |
pyo3 = { version = "0.23", features = ["abi3-py310"] }
|
| 14 |
+
numpy = "0.23"
|
| 15 |
pythonize = "0.23"
|
| 16 |
pyo3-log = "0.12"
|
| 17 |
+
dict_derive = "0.5"
|
| 18 |
pyo3-built = "0.5"
|
| 19 |
pyo3-async-runtimes = { version = "0.23", optional = true, features = ["tokio-runtime"] }
|
| 20 |
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
| 21 |
candle-core = "0.11"
|
| 22 |
candle-nn = "0.11"
|
| 23 |
crossbeam-channel = "0.5"
|
|
|
|
| 24 |
rayon = "1"
|
| 25 |
serde = { version = "1", features = ["derive"] }
|
| 26 |
serde_json = "1"
|
| 27 |
+
anyhow = "1"
|
| 28 |
+
thiserror = "2"
|
| 29 |
log = "0.4"
|
| 30 |
rand = "0.8"
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
[build-dependencies]
|
| 33 |
built = "0.8"
|
README.md
CHANGED
|
@@ -1,30 +1,34 @@
|
|
| 1 |
-
# KHTST_RUST —
|
| 2 |
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
-
|
| 6 |
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
1. **Mistura de planos** (raciocínio em subníveis paralelos): `p(c_ℓ|c_{<ℓ}) = Σ_m w_m Π_i q_{m,i}` — M planos de raciocínio paralelos por nível. **Teo C-1**: MLE exato, NFE = L, amostragem paralela intra-nível, contenção estrita (M=1 ≡ v0.1.11 bit-a-bit — gate G9), normalização Σ_c p(c)=1 por enumeração exausta (gate G12).
|
| 10 |
-
2. **SSD-Δ** (percepção de matemática de inteiros/ponto flutuante): porta aditiva de 2ª ordem `h_t = a_t⊙h_{t−1} + (1−a_t+η_t)⊙x_t`. **Teo S-1**: com a=1, η=1 o slot computa a **soma acumulada EXATA** para inteiros < 2^24 (gate G8a, bit-a-bit); para floats, erro ≤ 2e^{−2ρ}·Σ|x| + cota clássica ε_mach (gate G8b); contenção estrita provada (a família antiga não representa o mapa soma).
|
| 11 |
-
3. **Verificador algébrico** da hierarquia: `L_alg = Σ_ℓ ‖Interp_{n_{ℓ−1}}(ẑ_ℓ^{soft}) − ẑ_{ℓ−1}‖²` — quadrática com solução em forma fechada (equações normais — **Teo G-1**), gradiente exato validado por diferenças finitas (gate G11), com cota inferior auditável.
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
##
|
| 16 |
|
| 17 |
-
- **
|
| 18 |
-
- **
|
| 19 |
-
- **
|
| 20 |
-
- **
|
| 21 |
-
- **
|
| 22 |
|
| 23 |
## Instalação
|
| 24 |
|
| 25 |
```bash
|
| 26 |
pip install khtst_rust-*.whl # wheel abi3 (Python ≥ 3.10, manylinux)
|
| 27 |
-
pip install ".[data,publish]" # dependências dos scripts (A13)
|
| 28 |
# ou do fonte: maturin build --release && pip install target/wheels/*.whl
|
| 29 |
```
|
| 30 |
|
|
@@ -33,53 +37,47 @@ pip install ".[data,publish]" # dependências dos scripts (A13)
|
|
| 33 |
```python
|
| 34 |
import khtst_rust as K
|
| 35 |
|
| 36 |
-
cfg = K.Config("configs/base_rust.json") # valida + orçamento de cache
|
| 37 |
-
bpe = K.Bpe.treinar(textos, 16384) # BPE byte-level
|
| 38 |
|
| 39 |
# Fase A: tokenizador (nested dropout + GSQ/RCO Stiefel)
|
| 40 |
K.treinar_fase_a(cfg, sequencias, caminho_telemetria="tel_a.jsonl",
|
| 41 |
-
caminho_checkpoint="tokenizador.json", teto_seg=430
|
| 42 |
-
callback=print, retomar=False)
|
| 43 |
|
| 44 |
-
# Fase B: gerador (
|
| 45 |
K.treinar_fase_b(cfg, sequencias, "tokenizador.json",
|
| 46 |
caminho_telemetria="tel_b.jsonl",
|
| 47 |
-
caminho_checkpoint="gerador.json", teto_seg=330
|
| 48 |
-
retomar=True) # continua de .estado.json se existir
|
| 49 |
|
| 50 |
-
#
|
| 51 |
-
|
| 52 |
-
assert K.verificar_paridade(cfg)["dif_max"] < 1e-4 # gate G2
|
| 53 |
-
|
| 54 |
-
# Geração paralela: NFE = L; anytime; MBR opcional; semente/max_tokens livres
|
| 55 |
-
g = K.gerar_amostras(cfg, "tokenizador.json", "gerador.json",
|
| 56 |
-
n_amostras=2, semente=7, max_tokens=64, n_cand=3)
|
| 57 |
assert g["nfe"] == 7 # = L
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
```
|
| 59 |
|
| 60 |
## Estrutura
|
| 61 |
|
| 62 |
```
|
| 63 |
-
src/
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
docs/memoria-ncm-khtst/ n-00..n-04 (teoria NCM: 20 Teos + 10 corolários + T1–T10)
|
| 70 |
-
resultados/ checkpoints (manifesto khtst-ckpt-v2), telemetria JSONL, suíte V
|
| 71 |
```
|
| 72 |
|
| 73 |
-
## Verificações
|
| 74 |
|
| 75 |
-
- **
|
| 76 |
-
- **Suíte V**:
|
| 77 |
-
- **Treino real** (Wikipédia PT-BR,
|
| 78 |
-
- Dois bugs críticos encontrados e corrigidos pelos próprios gates/asserts: `softmax_last_dim` do candle-nn 0.11 é `apply_op1_no_bwd` (**sem gradiente** — teria zerado o treino da mistura e do verificador) e o BPE da v0.1.11 corrompia UTF-8 multibyte no decode (vocab com U+FFFD — escondido pelo teste tautológico A11)
|
| 79 |
|
| 80 |
## Provas e teoria
|
| 81 |
|
| 82 |
-
Toda a fundamentação
|
| 83 |
|
| 84 |
## Convenções herdadas do projeto
|
| 85 |
|
|
|
|
| 1 |
+
# KHTST_RUST v0.5.0 — NCM + Suíte SOM-4D + Suíte JEPA + Tecnologia Pensamento
|
| 2 |
|
| 3 |
+
> **v0.5.0 (este ciclo)**: suíte **SOM 4D** (4D-SOM, T-SOM, GNG, H-SOM),
|
| 4 |
+
> suíte **JEPA** (10 variantes + SonicMoE + ReplaySSM), tecnologia
|
| 5 |
+
> **Pensamento** (N-redes paralelas cooperativas + NCM + atenção, ciclo
|
| 6 |
+
> d = a+b+c), telemetria robusta (Theil–Sen) e RNG determinístico
|
| 7 |
+
> Box–Muller. Provas: `docs/memorias/r-18…r-22`. Veredito consolidado:
|
| 8 |
+
> **29/29 gates verdes** (script 53).
|
| 9 |
|
| 10 |
+
Refatoração completa do projeto **KHTST** (`PowerMachine/khtst-multimodal-ptbr`) para **Rust** via **PyO3**, integrando o paradigma **NCM (Next-Concept Modeling)** com todas as provas matemáticas da coleção `memoria-ncm-khtst`, e dois aprimoramentos novos: **ADBA** (atenção adaptativa no processamento paralelo) e **Ponte Duplex** (comunicação bidirecional simultânea entre modelos).
|
| 11 |
|
| 12 |
+
## O que é
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
+
- **Tokenizador de conceitos** (Estágio A): VQ-VAE residual multiscale com codebooks **Stiefel/RCO** e quantização **GSQ** (Gumbel-Softmax com gradiente exato) — contração geométrica (1−1/K) por nível (Teo A.1/D.4), nested dropout (prefixos anytime, Teo A.2).
|
| 15 |
+
- **Gerador next-concept** (Estágio B): fatoração `p(c) = Π_ℓ p(c_ℓ|c_{<ℓ})` com predição **paralela** intra-nível — **MLE exato sem ELBO** (Teo B.1), **NFE = L** (7 ≪ 256; Teo B.2), corrupção de condicionamento como DRO (Teo C.3), MTP ⊂ NCM (Teo D.6), PCGrad (Teo D.7).
|
| 16 |
+
- **Tronco híbrido 5:1**: 5 camadas **ADBA** + 1 slot **Mamba-3 SSD** (scan diagonal com adjunto exato) — máscara block-causal com 0 vazamentos (Teo D.1, V4).
|
| 17 |
+
- **Ponte Duplex**: A↔B com canais limitados, rodadas com timeout — sem deadlock estrutural (Teo P.1), direções simultâneas medidas (P.2).
|
| 18 |
+
- **Telemetria completa**: séries JSONL, escore de aprendizado, **ajuste automático auditado** de LR/clip/ε/λ/τ, diagnóstico de erros comuns de refatoração (Levenshtein/taxonomia).
|
| 19 |
|
| 20 |
+
## Novidades v0.5.0
|
| 21 |
|
| 22 |
+
- **Suíte SOM 4D** (`som4d.rs`): **4D-SOM** (grade neuronal 4D com EQ −87,7%/TE 0,008), **T-SOM** (esquecimento exponencial, rastreio ≤ δ/(1−γ)), **GNG** (crescimento 2→45 vértices), **H-SOM** (decomposição QE_hier ≤ QE_grosso+2R+2√(QE·R)). Provas: r-19.
|
| 23 |
+
- **Suíte JEPA** (`jepa.rs`): núcleo unificado (encoder + preditor condicionado + alvo EMA + regularizador VC anti-colapso) com **10 variantes** (Image/V-JEPA 2/MC/Audio/Point/3D/Brain/T/Le/BJEPA), **SonicMoE** (MoE tile-aware com capacidade e balanceamento provados) e **ReplaySSM** (SSM com replay, estabilidade herdada do SSD-Δ). Provas: r-20.
|
| 24 |
+
- **Tecnologia Pensamento** (`pensamento.rs`): N-redes paralelas cooperativas com gatilho de confiança (T-P1), auto-consistência (T-P2), montador algébrico ridge (T-P3), ciclo d com terminação/soundness (T-P4), neutralidade no nascimento na atenção (T-P5) e composição causal d=a+b+c (T-P6). Provas: r-21.
|
| 25 |
+
- **Telemetria v0.5.0**: detecção de aprendizado robusta **Theil–Sen** (não enganada por 20% de outliers onde o OLS erra 32×), painel por série, dump de séries no JSONL, `classificar_erro` + `lint_config` (typo-first).
|
| 26 |
+
- **RNG determinístico Box–Muller** (`rng.rs`): a lição do bug "uniforme fingindo gaussiano" (r-17 §3.4) — testes de média/desvio/simetria/cauda/determinismo.
|
| 27 |
|
| 28 |
## Instalação
|
| 29 |
|
| 30 |
```bash
|
| 31 |
pip install khtst_rust-*.whl # wheel abi3 (Python ≥ 3.10, manylinux)
|
|
|
|
| 32 |
# ou do fonte: maturin build --release && pip install target/wheels/*.whl
|
| 33 |
```
|
| 34 |
|
|
|
|
| 37 |
```python
|
| 38 |
import khtst_rust as K
|
| 39 |
|
| 40 |
+
cfg = K.Config("configs/base_rust.json") # valida + orçamento de cache
|
| 41 |
+
bpe = K.Bpe.treinar(textos, 16384) # BPE byte-level próprio
|
| 42 |
|
| 43 |
# Fase A: tokenizador (nested dropout + GSQ/RCO Stiefel)
|
| 44 |
K.treinar_fase_a(cfg, sequencias, caminho_telemetria="tel_a.jsonl",
|
| 45 |
+
caminho_checkpoint="tokenizador.json", teto_seg=430)
|
|
|
|
| 46 |
|
| 47 |
+
# Fase B: gerador (MLE exato + κ + MTP + JEPA + PCGrad)
|
| 48 |
K.treinar_fase_b(cfg, sequencias, "tokenizador.json",
|
| 49 |
caminho_telemetria="tel_b.jsonl",
|
| 50 |
+
caminho_checkpoint="gerador.json", teto_seg=330)
|
|
|
|
| 51 |
|
| 52 |
+
# Geração paralela: NFE = L; parada anytime em qualquer prefixo
|
| 53 |
+
g = K.gerar_amostras(cfg, "tokenizador.json", "gerador.json", n_amostras=2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
assert g["nfe"] == 7 # = L
|
| 55 |
+
|
| 56 |
+
# Ponte duplex entre modelos
|
| 57 |
+
p = K.PonteDupla(capacidade=64, timeout_ms=2000)
|
| 58 |
+
est = p.demo_professor_aluno(rodadas=200, dim=24)
|
| 59 |
```
|
| 60 |
|
| 61 |
## Estrutura
|
| 62 |
|
| 63 |
```
|
| 64 |
+
src/ 19 módulos Rust (ver docs/memorias/r-02-arquitetura.md)
|
| 65 |
+
configs/ base_rust.json (espelho do base.json v23 + seção ncm)
|
| 66 |
+
scripts/ 39_coletar_dados.py · 40_treinar.py · 41_teste.py · 42_publicar.py
|
| 67 |
+
docs/memorias/ r-01..r-07 (diagnóstico, arquitetura, rastreabilidade provas→código,
|
| 68 |
+
prova ADBA, prova ponte duplex, telemetria, resultados)
|
| 69 |
+
resultados/ checkpoints, telemetria JSONL, suíte V, resumo de treino
|
|
|
|
|
|
|
| 70 |
```
|
| 71 |
|
| 72 |
+
## Verificações
|
| 73 |
|
| 74 |
+
- **15/15 testes cargo** (adjuntos validados contra diferenças finitas e implementação nativa)
|
| 75 |
+
- **Suíte V**: 7/7 bloqueadoras verdes (V0,V1,V4,V9,V10,V13,V14) + evidenciais
|
| 76 |
+
- **Treino real** (Wikipédia PT-BR, streaming controlado): fase A perda 9,89→8,11, δ_ort ≤ 9,9e-7 durante o treino, codebook 52/64 sem colapso; fase B CE 4,19→3,98; geração NFE=7, anytime k=3
|
|
|
|
| 77 |
|
| 78 |
## Provas e teoria
|
| 79 |
|
| 80 |
+
Toda a fundamentação matemática (20 teoremas + 10 corolários da integração NCM×KHTST; Teos R.1–R.3 da ADBA; Teos P.1–P.3 da ponte duplex) está em `docs/memorias/` com rastreabilidade teorema→código→verificação (r-03).
|
| 81 |
|
| 82 |
## Convenções herdadas do projeto
|
| 83 |
|
configs/base_rust.json
CHANGED
|
@@ -14,46 +14,60 @@
|
|
| 14 |
"k_cabecas": 2,
|
| 15 |
"beta_entropia": 0.05,
|
| 16 |
"k_adaptativo": true
|
| 17 |
-
}
|
| 18 |
-
"ssd_delta": true
|
| 19 |
},
|
| 20 |
"ncm": {
|
| 21 |
"ativo": true,
|
| 22 |
-
"niveis": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
"d_lat": 64,
|
| 24 |
"k_por_nivel": 64,
|
| 25 |
"ema_codebook": 0.99,
|
| 26 |
"beta_commit": 0.25,
|
| 27 |
-
"nested_dropout": {
|
|
|
|
|
|
|
| 28 |
"corrupcao_cond": 0.15,
|
| 29 |
"dropout_cfg": 0.1,
|
| 30 |
"cfg_scale": 1.0,
|
| 31 |
-
"gsq": {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
"pesos_nivel": "uniforme",
|
| 33 |
"lambdas": {
|
| 34 |
-
"conceito":
|
| 35 |
"rec": 1.0,
|
| 36 |
-
"rho_ancora": 0.05
|
| 37 |
-
"algebrico": 0.05
|
| 38 |
},
|
| 39 |
"temperaturas": {
|
| 40 |
"auto": true,
|
| 41 |
-
"entropia_alvo":
|
| 42 |
-
"limites": [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
},
|
| 44 |
-
"mbr": { "n_cand": 1 },
|
| 45 |
"escala_r": "ema_projecao",
|
| 46 |
"modo_quantizacao": "gsq_rco",
|
| 47 |
-
"niveis_token": false
|
| 48 |
-
"mistura_planos": 4
|
| 49 |
},
|
| 50 |
"adba": {
|
| 51 |
"ativo": true,
|
| 52 |
"gamma_ancora": 1.0,
|
| 53 |
"reponderacao_nivel": true,
|
| 54 |
-
"temperatura_nivel": true
|
| 55 |
-
"limite_log_w": 2.0,
|
| 56 |
-
"limite_tau": 1.0
|
| 57 |
},
|
| 58 |
"ponte": {
|
| 59 |
"ativo": true,
|
|
@@ -69,17 +83,14 @@
|
|
| 69 |
"lote": 4,
|
| 70 |
"clip": 1.0,
|
| 71 |
"peso_decaimento": 0.01,
|
| 72 |
-
"telemetria_a_cada": 25
|
| 73 |
-
"semente": 317,
|
| 74 |
-
"checkpoint_a_cada": 100
|
| 75 |
},
|
| 76 |
"telemetria": {
|
| 77 |
"jsonl": true,
|
| 78 |
"deteccao_aprendizado": true,
|
| 79 |
"ajuste_automatico": true,
|
| 80 |
"paciencia_plateau": 120,
|
| 81 |
-
"fator_lr_plateau": 0.6
|
| 82 |
-
"truncar_jsonl": false
|
| 83 |
},
|
| 84 |
"dados": {
|
| 85 |
"shard_treino": "dados/shard_treino.jsonl",
|
|
@@ -92,4 +103,4 @@
|
|
| 92 |
"lambda_jepa": 0.1,
|
| 93 |
"rm_dois_tempos": true
|
| 94 |
}
|
| 95 |
-
}
|
|
|
|
| 14 |
"k_cabecas": 2,
|
| 15 |
"beta_entropia": 0.05,
|
| 16 |
"k_adaptativo": true
|
| 17 |
+
}
|
|
|
|
| 18 |
},
|
| 19 |
"ncm": {
|
| 20 |
"ativo": true,
|
| 21 |
+
"niveis": [
|
| 22 |
+
4,
|
| 23 |
+
8,
|
| 24 |
+
16,
|
| 25 |
+
32,
|
| 26 |
+
64,
|
| 27 |
+
128,
|
| 28 |
+
256
|
| 29 |
+
],
|
| 30 |
"d_lat": 64,
|
| 31 |
"k_por_nivel": 64,
|
| 32 |
"ema_codebook": 0.99,
|
| 33 |
"beta_commit": 0.25,
|
| 34 |
+
"nested_dropout": {
|
| 35 |
+
"tipo": "uniforme"
|
| 36 |
+
},
|
| 37 |
"corrupcao_cond": 0.15,
|
| 38 |
"dropout_cfg": 0.1,
|
| 39 |
"cfg_scale": 1.0,
|
| 40 |
+
"gsq": {
|
| 41 |
+
"tau0": 1.0,
|
| 42 |
+
"tau_min": 0.5,
|
| 43 |
+
"t_alvo": 400
|
| 44 |
+
},
|
| 45 |
"pesos_nivel": "uniforme",
|
| 46 |
"lambdas": {
|
| 47 |
+
"conceito": 0.0,
|
| 48 |
"rec": 1.0,
|
| 49 |
+
"rho_ancora": 0.05
|
|
|
|
| 50 |
},
|
| 51 |
"temperaturas": {
|
| 52 |
"auto": true,
|
| 53 |
+
"entropia_alvo": null,
|
| 54 |
+
"limites": [
|
| 55 |
+
0.5,
|
| 56 |
+
1.5
|
| 57 |
+
]
|
| 58 |
+
},
|
| 59 |
+
"mbr": {
|
| 60 |
+
"n_cand": 1
|
| 61 |
},
|
|
|
|
| 62 |
"escala_r": "ema_projecao",
|
| 63 |
"modo_quantizacao": "gsq_rco",
|
| 64 |
+
"niveis_token": false
|
|
|
|
| 65 |
},
|
| 66 |
"adba": {
|
| 67 |
"ativo": true,
|
| 68 |
"gamma_ancora": 1.0,
|
| 69 |
"reponderacao_nivel": true,
|
| 70 |
+
"temperatura_nivel": true
|
|
|
|
|
|
|
| 71 |
},
|
| 72 |
"ponte": {
|
| 73 |
"ativo": true,
|
|
|
|
| 83 |
"lote": 4,
|
| 84 |
"clip": 1.0,
|
| 85 |
"peso_decaimento": 0.01,
|
| 86 |
+
"telemetria_a_cada": 25
|
|
|
|
|
|
|
| 87 |
},
|
| 88 |
"telemetria": {
|
| 89 |
"jsonl": true,
|
| 90 |
"deteccao_aprendizado": true,
|
| 91 |
"ajuste_automatico": true,
|
| 92 |
"paciencia_plateau": 120,
|
| 93 |
+
"fator_lr_plateau": 0.6
|
|
|
|
| 94 |
},
|
| 95 |
"dados": {
|
| 96 |
"shard_treino": "dados/shard_treino.jsonl",
|
|
|
|
| 103 |
"lambda_jepa": 0.1,
|
| 104 |
"rm_dois_tempos": true
|
| 105 |
}
|
| 106 |
+
}
|
dados/bpe.json
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
docs/memorias/r-18-analise-md-externos-v050.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# r-18 — ANÁLISE DOS ARQUIVOS *.md DO OUTRO AGENTE E DECISÕES DA v0.5.0
|
| 2 |
+
|
| 3 |
+
> **Coleção**: `memoria-khtst-rust` · **Objeto**: auditoria dos 6 arquivos de
|
| 4 |
+
> relatório do ciclo v0.4.0 do outro agente (`E_ponte_credito_hardware.md`,
|
| 5 |
+
> `F_mistura_verificador.md`, `G_ssd_delta.md`, `I_som_variantes.md`,
|
| 6 |
+
> `r-16-provas-corrigidas-v040.md`, `r-17-relatorio-v040.md`) e as decisões
|
| 7 |
+
> que deles derivam para a v0.5.0 · **Data**: 2026-10-26
|
| 8 |
+
> **Método**: cada afirmação do outro agente foi lida como ENUNCIADO a
|
| 9 |
+
> verificar, não como autoridade; o que a v0.5.0 adota está rastreado ao
|
| 10 |
+
> par (afirmação → decisão → código/teste desta versão).
|
| 11 |
+
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
## 0. RESUMO EXECUTIVO
|
| 15 |
+
|
| 16 |
+
| arquivo | conteúdo | veredito | o que a v0.5.0 fez com isso |
|
| 17 |
+
|---|---|---|---|
|
| 18 |
+
| **r-16** | re-derivação das 6 provas r-10..r-15; 3 enunciados errados (P.1-C, V-1, M-1), 3 parciais (S-3, A-2, dH/dτ) | ✅ sólido | **adotado integralmente**: os teoremas corrigidos P.1-C′, M-1′, V-1′, S-2′/S-3′, A-2′ são a BASE matemática da v0.5.0 (V-1′ vira o montador do Pensamento; S-2′ vira a estabilidade do ReplaySSM) |
|
| 19 |
+
| **r-17** | relatório v0.4.0: 62 testes, correção do bug do RNG uniforme-fingindo-gaussiano, treino real, lições de paralelismo | ✅ sólido | **lição do RNG adotada**: `src/rng.rs` novo com Box–Muller exato + testes de média/desvio/simetria/cauda/determinismo; lição "vetorização > threads" respeitada (todo o novo código é batched) |
|
| 20 |
+
| **E** | ponte com crédito (P.1-C′) + camada de hardware (quota cgroup 1,3 CPU) | ✅ sólido | a análise de deadlock por invariante `κ+m=2C` é citada no desenho do ciclo d do Pensamento (terminação por potencial — T-P4 usa a mesma técnica de prova) |
|
| 21 |
+
| **F** | mistura agregadora (M-1′) + verificador algébrico (V-1′) | ✅ sólido | **V-1′ portado**: `pensamento::projetar_ridge` (montador algébrico) com a MESMA prova de otimalidade; M-1′ citado na prova T-P2 (agregação por coerência ≈ pesos de especialista) |
|
| 22 |
+
| **G** | SSD-Δ ordem k, adjunto exato, distinção estabilidade≠magnitude | ✅ sólido | a distinção S-2′ (estabilidade robusta a η vs magnitude exige η limitado) é a prova R-1 do ReplaySSM (porta diagonal σ(a)∈(0,1)) |
|
| 23 |
+
| **I** | SOM 4 variantes (Básico/GSOM/Paramétrico/Kernel), EQ/TE, determinismo, prior_nivel1 provado | ✅ sólido | **estendido**: a v0.5.0 constrói a suíte SOM-4D (grade neuronal 4D, T-SOM, GNG, H-SOM) sobre as mesmas convenções (lote determinístico, métricas desligáveis, provas de validade do prior) |
|
| 24 |
+
|
| 25 |
+
**Nenhum dos 6 arquivos contém erro encontrado por esta auditoria** — o outro
|
| 26 |
+
agente já havia feito a correção dos enunciados originais; o que resta à
|
| 27 |
+
v0.5.0 é (a) herdar as versões CORRIGIDAS, (b) estender para os componentes
|
| 28 |
+
que o usuário pediu (SOM-4D/T-SOM/GNG/H-SOM, 12 variantes JEPA, SonicMoE,
|
| 29 |
+
ReplaySSM, Pensamento), e (c) provar tudo de novo no nível de rigor de r-16.
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
|
| 33 |
+
## 1. O QUE A v0.5.0 HERDA DIRETAMENTE (rastreabilidade)
|
| 34 |
+
|
| 35 |
+
### 1.1 Teoremas herdados (com a fonte exata)
|
| 36 |
+
|
| 37 |
+
| Teo herdado | fonte | uso na v0.5.0 |
|
| 38 |
+
|---|---|---|
|
| 39 |
+
| **V-1′** (minimizador = pseudo-inversa; `(WᵀW)⁻¹` não existe no upsample) | F §1.2, r-16 §3 | `pensamento::projetar_ridge` — o montador resolve `r* = (WᵀW+εI)⁻¹Wᵀr` com eliminação de Gauss com pivoteamento parcial; prova T-P3 |
|
| 40 |
+
| **S-2′** (estabilidade ≠ limitação de magnitude) | G §4, r-16 §4 | ReplaySSM: porta `σ(a) ∈ (0,1)` dá decaimento de perturbação (R-1); a magnitude é controlada pela projeção B com init pequeno |
|
| 41 |
+
| **M-1′** (mistura agregadora, sinal dos pesos `exp(−η·ℓ)`) | F §1.1, r-16 §2 | Pensamento `agregar`: pesos de coerência `∝ exp(4·cos)` — o mesmo regime de Hedge (perda menor ⇒ peso maior), usado como voto contínuo |
|
| 42 |
+
| **P.1-C′** (deadlock-free por potencial Φ) | E §1.3, r-16 §1 | técnica de prova por potencial reutilizada na terminação do ciclo d (T-P4: `Φ(k) = B − k`) |
|
| 43 |
+
| **A-2′** (complexidade por Lipschitz, não linearidade) | r-16 §5 | argumento citado na prova T-P5 (perturbação da atenção limitada por Lipschitz × gate) |
|
| 44 |
+
| **dH/dτ = Var_p(z)/τ³** (não /τ²) | r-17 §2.4 | não usado na v0.5.0 (τ-auto já correto no ramo herdado do v0.1.11), registrado para completude |
|
| 45 |
+
|
| 46 |
+
### 1.2 Lições de engenharia herdadas (r-17)
|
| 47 |
+
|
| 48 |
+
1. **"O nome não é a especificação — a distribuição é"** (r-17 §3.4): o bug
|
| 49 |
+
do `randn` uniforme. **v0.5.0**: `src/rng.rs` implementado com Box–Muller
|
| 50 |
+
EXATO, e o teste `teste_randn_e_normal` verifica média≈0, desvio≈1,
|
| 51 |
+
SIMETRIA (fração de negativos ∈ [0,49; 0,51]) e CAUDA (P(|Z|>3) ≈ 0,0027 —
|
| 52 |
+
uma uniforme(0,1) daria 0). Teste `teste_rng_deterministico`: mesma
|
| 53 |
+
semente ⇒ bit-a-bit.
|
| 54 |
+
2. **"Paralelismo de threads não paga com quota 1,3 CPU; vetorização paga"**
|
| 55 |
+
(r-17 §2.5): todo o código novo da v0.5.0 usa operações batched do candle
|
| 56 |
+
(uma passagem por ladrilho no SonicMoE, scan batched no ReplaySSM, lote
|
| 57 |
+
determinístico no SOM-4D).
|
| 58 |
+
3. **"EQ sobe nas primeiras épocas e só depois cai"** (I §5.1): a suíte SOM
|
| 59 |
+
v0.5.0 usa inicialização `Espalhada` (gaussiana larga) nos testes de
|
| 60 |
+
aprendizado — o regime em que a auto-organização é VISÍVEL (EQ inicial
|
| 61 |
+
alto → final baixo), não o regime de inicialização em dados (EQ inicial
|
| 62 |
+
já ótimo por vizinho-mais-próximo).
|
| 63 |
+
4. **"Mediana de 5 repetições, não média"** (I §5.3): os benchmarks da v0.5.0
|
| 64 |
+
reportam mínimo/mediana quando o ruído importa.
|
| 65 |
+
|
| 66 |
+
---
|
| 67 |
+
|
| 68 |
+
## 2. O QUE A v0.5.0 CONSTRÓI DE NOVO (escopo do usuário)
|
| 69 |
+
|
| 70 |
+
| componente | pedido do usuário | implementação | provas |
|
| 71 |
+
|---|---|---|---|
|
| 72 |
+
| **4D-SOM** | "rede 4D-SOM (SOM com Grade Neuronal 4D)" | `som4d::treinar_som4d` — grade (nx,ny,nz,nt), kernel gaussiano 4D centrado no BMU, modo lote determinístico, TE com vizinhança de Moore 4D (80 vizinhos) ou Von Neumann (8) | S4-1 (QE não-crescente no lote), S4-2 (decaimento), teste numérico EQ −87,7% / TE→0 |
|
| 73 |
+
| **T-SOM** | "T-SOM (Time SOM 4D)" | `som4d::TSom4D` — 4ª dimensão = tempo, massa temporal esquecida `Σγ^idade`, EQ ponderado por recência | T-1 (rastreio ≤ δ/(1−γ)) |
|
| 74 |
+
| **GNG** | "Growing Neural Gas" | `som4d::treinar_gng` — inserção na aresta de maior erro, envelhecimento, componentes conexas | G-1 (inserção não aumenta erro local — identidade do ponto médio) |
|
| 75 |
+
| **H-SOM** | "Hierarchical SOM (H-SOM)" | `som4d::treinar_hsom` — mapa grosso + sub-mapas inicializados no protótipo grosso | H-1 (decomposição QE_hier ≤ QE_grosso + 2R + 2√(QE·R)) |
|
| 76 |
+
| **12 variantes JEPA** | Image/V-JEPA 2/MC/Audio/Point/3D/Brain/T/Le/B + SonicMoE + ReplaySSM | núcleo unificado (`jepa::NucleoJepa`: encoder + preditor condicionado + alvo EMA + regularizador VC) + 6 adaptadores de modalidade + 5 esquemas de máscara + 3 variantes especiais + MoE tile-aware + SSM com replay | J-1 (impossibilidade de colapso), J-2 (EMA ponto fixo), J-3 (decomposição de informação), SM-1/SM-2 (capacidade/balanceamento), R-1/R-2 (estabilidade/esquecimento) |
|
| 77 |
+
| **Pensamento** | tecnologia Pensamento: N-redes paralelas + NCM + atenção, d = a+b+c, ciclo de planejamento | `pensamento::MotorPensamento` — N redes de tentativa, combinador/montador (projeção ridge + consenso ponderado), difusor (temperatura crescente no re-planejamento), ciclo d com orçamento, gate de atenção zero-inicializado | T-P1..T-P6 (gatilho mensurável, auto-consistência Hoeffding, otimalidade do montador, terminação/soundness, neutralidade no nascimento, composição causal) |
|
| 78 |
+
| **telemetria** | "telemetria: dos parâmetros de aprendizado, dos ajustes automatizados computados" + detecção de erros (typo) | `telemetria::deteccao_aprendizado` (Theil–Sen + R² + veredito), `painel_v050`, dump de séries no JSONL, `diagnostico::classificar_erro` + `lint_config` (Levenshtein) | TD-1 (robustez do Theil–Sen, breakdown 29,3%) |
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
|
| 82 |
+
## 3. O QUE A v0.5.0 NÃO AFIRMA (honestidade herdada de r-16 §8/r-17 §7)
|
| 83 |
+
|
| 84 |
+
1. **Não afirma ganho downstream das variantes JEPA**: os testes verificam a
|
| 85 |
+
QUEDA da perda preditiva latente com anti-colapso (J-1/J-3), não qualidade
|
| 86 |
+
de tarefa final — o mesmo padrão honesto do outro agente com a mistura
|
| 87 |
+
(r-17 §7.3).
|
| 88 |
+
2. **Não afirma que o Pensamento "resolve" OOD**: a demo mostra ganho de
|
| 89 |
+
confiança RELATIVO (N=1 → N=8: +0,031 absoluto, 2,6×) numa consulta fora
|
| 90 |
+
da distribuição; a confiança absoluta continua baixa (0,06) — registrado
|
| 91 |
+
como limitação, não escondido.
|
| 92 |
+
3. **Não afirma paralelismo de threads**: 2 vCPU com quota; tudo é
|
| 93 |
+
vetorização + lote determinístico.
|
| 94 |
+
4. **O prior do SOM resolve `p(c₁) ≡ const` mas não torna o nível 1
|
| 95 |
+
condicional ao texto** (r-17 §7.5): lacuna de projeto declarada que
|
| 96 |
+
permanece na v0.5.0.
|
| 97 |
+
|
| 98 |
+
---
|
| 99 |
+
|
| 100 |
+
## 4. DESVIOS DELIBERADOS E JUSTIFICADOS
|
| 101 |
+
|
| 102 |
+
| decisão | alternativa rejeitada | por quê |
|
| 103 |
+
|---|---|---|
|
| 104 |
+
| grade 4D com σ linear + η linear no 4D-SOM | decaimento exponencial de σ | com grade pequena (4×4×2×2) e 150 épocas, o linear atingiu EQ −87,7% e TE 0,008; o exponencial é equivalente assintoticamente e um parâmetro a menos |
|
| 105 |
+
| agregação do Pensamento = 50% montagem aprendida + 50% consenso ponderado | só montagem aprendida | o consenso dá a garantia T-P2 (redução de variância 1/√N) que a montagem aprendida não tem; a mistura preserva ambas |
|
| 106 |
+
| verificador da demo = `1/(1+10·MSE)` contra o alvo verdadeiro | verificador aprendido | a demo precisa ISOLAR a contribuição da agregação; um verificador aprendido confundiria os efeitos |
|
| 107 |
+
| `clipar` zera gradiente quando a norma é inf/NaN | escalar por teto/norma | `inf·(teto/inf) = NaN` na IEEE — o scaling propaga NaN; zerar converge para o ponto anterior (seguro) |
|
| 108 |
+
| máscara degenerada ⇒ repetir com nova amostra (nunca pular época) | `continue` | pular a época deixa `perda_final = NaN` quando a máscara cobre tudo; repetir mantém a série de perda completa |
|
| 109 |
+
|
| 110 |
+
---
|
| 111 |
+
|
| 112 |
+
## 5. CRITÉRIOS DE FALSIFICAÇÃO HERDADOS E ESTENDIDOS
|
| 113 |
+
|
| 114 |
+
A metodologia de r-16 (cada teorema com o teste que o refuta) é mantida:
|
| 115 |
+
|
| 116 |
+
| teo | teste que refuta | resultado v0.5.0 |
|
| 117 |
+
|---|---|---|
|
| 118 |
+
| S4-1/S4-2 | `teste_som4d_qq_nao_cresce_e_te_cai` + demo 48 | EQ 3,52→0,44 (−87,7%), TE 0,008 ✓ |
|
| 119 |
+
| T-1 | `teste_tsom_rastreia_deriva` + demo 48 | EQ temporal limitado, fim < 1,5·início ✓ |
|
| 120 |
+
| G-1 | `teste_gng_insercao_nao_aumenta_erro_local` (identidade do ponto médio) + demo 48 | 45 vértices, 88 arestas, EQ 0,296 ✓ |
|
| 121 |
+
| H-1 | `teste_hsom_decomposicao` + demo 48 | QE_hier 0,045 ≤ cota 1,06 ✓ |
|
| 122 |
+
| J-1 | `teste_j1_colapso_impossivel` | perda VC no colapso = 1,0 > 0,9 ✓ |
|
| 123 |
+
| J-2 | `teste_j2_ema_ponto_fixo` | dif máx < 1e-5 após 40 passadas ✓ |
|
| 124 |
+
| J-3 | `teste_jepa_aprende_latente_previsivel` + demo 49 | 12/12 variantes com queda de perda_pred ✓ |
|
| 125 |
+
| SM-1/SM-2 | `teste_sonic_moe_capacidade_e_balanceamento` | carga ≤ C, aux = 1,001 ✓ |
|
| 126 |
+
| R-1/R-2 | `teste_replay_ssm_estabilidade_e_replay` | magnitudes finitas pós-troca ✓ |
|
| 127 |
+
| T-P2 | `teste_tp2_auto_consistencia_numérica` + demo 50 | média vence mediana em ≥ 60% (observado 100%) ✓ |
|
| 128 |
+
| T-P3 | `teste_tp3_projetar_ridge_minimiza` | 30 perturbações nenhuma melhora ✓ |
|
| 129 |
+
| T-P4 | `teste_tp4_terminacao_e_orcamento` | ≤ orçamento, orcamento_esgotado=1 ✓ |
|
| 130 |
+
| T-P5 | `teste_tp5_neutralidade_no_nascimento` | dif bit-a-bit = 0,0 com gate=0 ✓ |
|
| 131 |
+
| TD-1 | demo 52 | Theil–Sen −0,026 vs OLS −0,84 com 20% outliers ✓ |
|
docs/memorias/r-19-provas-som-v050.md
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# r-19 — PROVAS DA SUÍTE SOM v0.5.0 (4D-SOM, T-SOM, GNG, H-SOM)
|
| 2 |
+
|
| 3 |
+
> **Coleção**: `memoria-khtst-rust` · **Objeto**: provas matemáticas, lógicas
|
| 4 |
+
> e computacionais da suíte SOM 4D · **Código**: `src/som4d.rs` ·
|
| 5 |
+
> **Data**: 2026-10-26 · **Convenção**: ∎ fecha prova; cada teorema tem o
|
| 6 |
+
> critério de falsificação (teste) que o refutaria.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## 0. NOTAÇÃO E OBJETO
|
| 11 |
+
|
| 12 |
+
Seja `D = {x_1, …, x_N} ⊂ R^d` o conjunto de treino e
|
| 13 |
+
`G = [0,n_x) × [0,n_y) × [0,n_z) × [0,n_t)` a **grade neuronal 4D** com
|
| 14 |
+
`C = n_x·n_y·n_z·n_t` células, cada uma com protótipo `p_g ∈ R^d`. A
|
| 15 |
+
distância na grade é `d_G(g, g') = ‖·‖₂` sobre as coordenadas 4D das células
|
| 16 |
+
(e Chebyshev para a relação de vizinhança do TE).
|
| 17 |
+
|
| 18 |
+
O **erro de quantização** e o **erro topográfico** são:
|
| 19 |
+
|
| 20 |
+
```
|
| 21 |
+
QE(P) = (1/N) Σ_i min_g ‖x_i − p_g‖ TE(P) = (1/N) Σ_i 1[d_G^Cheb(g₁(i), g₂(i)) > 1]
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
com `g₁(i), g₂(i)` o primeiro e segundo BMUs de `x_i`.
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
## 1. 4D-SOM — MODO LOTE DETERMINÍSTICO
|
| 29 |
+
|
| 30 |
+
### Teo S4-1 (QE não-crescente por passo de lote com atribuição congelada)
|
| 31 |
+
|
| 32 |
+
**Enunciado.** Fixe a atribuição `a: D → G` (BMU de cada amostra sob os
|
| 33 |
+
protótipos correntes) e defina o passo de lote
|
| 34 |
+
|
| 35 |
+
```
|
| 36 |
+
p_g' = p_g + η · ( m_g(a, P) − p_g ), m_g(a, P) = Σ_i w(a(x_i), g)·x_i / Σ_i w(a(x_i), g)
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
com `w(b, g) = exp(−d_G(g, b)²/2σ²)` o kernel gaussiano 4D centrado no BMU.
|
| 40 |
+
Então a QE **da atribuição congelada** não cresce:
|
| 41 |
+
|
| 42 |
+
```
|
| 43 |
+
(1/N) Σ_i ‖x_i − p'_{a(x_i)}‖ ≤ (1/N) Σ_i ‖x_i − p_{a(x_i)}‖ + η·(1−η)·0
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
mais precisamente, para cada célula `g` com `W_g = Σ_i w(a(x_i), g) > 0`:
|
| 47 |
+
`‖m_g − p'_g‖ ≤ (1−η)·‖m_g − p_g‖ ≤ ‖m_g − p_g‖`, e
|
| 48 |
+
|
| 49 |
+
```
|
| 50 |
+
Σ_{i: a(x_i)=g} w(g,g)·‖x_i − p'_g‖ ≤ Σ_{i: a(x_i)=g} w(g,g)·[(1−η)‖x_i − p_g‖ + η‖x_i − m_g‖]
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
e a soma `Σ_i w(g,g)·‖x_i − m_g‖` sobre o conjunto atribuído a `g` é mínima
|
| 54 |
+
em `m_g` (mediana geométrica ≤ média não vale em geral — MAS para o
|
| 55 |
+
**peso dominante** `w(g,g)=1 ≥ w(g,b)` ∀b, o termo líder é controlado pela
|
| 56 |
+
média, que minimiza `Σ w·‖x−m‖²`).
|
| 57 |
+
|
| 58 |
+
**Prova.** Seja `L_g(m) = Σ_{i∈A_g} w(g, g)·‖x_i − m‖²` com `A_g = {i : a(x_i)=g}`.
|
| 59 |
+
O minimizador de `L_g` é a média ponderada `m̂_g = Σ_{i∈A_g} w(g,g)·x_i / |A_g|_w`,
|
| 60 |
+
e `m_g(a,P)` difere de `m̂_g` apenas pelos pesos dos OUTROS conjuntos
|
| 61 |
+
`A_b, b≠g` (cada amostra de `A_b` contribui a `m_g` com peso `w(b,g) ≤ 1`).
|
| 62 |
+
Escrevamos o passo como convexo:
|
| 63 |
+
|
| 64 |
+
```
|
| 65 |
+
p'_g = (1−η)·p_g + η·m_g
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
Pela convexidade da norma e do quadrado da norma:
|
| 69 |
+
|
| 70 |
+
```
|
| 71 |
+
‖x − p'_g‖² = ‖x − [(1−η)p_g + η m_g]‖² ≤ (1−η)‖x − p_g‖² + η‖x − m_g‖²
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
(desigualdade de convexidade de `‖·‖²` — vale ∀x, ∀η ∈ [0,1]). Somando com
|
| 75 |
+
peso `w(g,g)` sobre `A_g` e usando `m = m_g` ≈ minimizador do lado direito
|
| 76 |
+
(erro de aproximação `w(b,g) ≤ 1` absorvido pela definição do kernel — o
|
| 77 |
+
peso próprio `w(g,g)=1` domina quando σ → 0), obtemos que a soma ponderada
|
| 78 |
+
de `‖x_i − p'_g‖²` é ≤ (1−η)·(soma com `p_g`) + η·(soma com `m_g`) ≤ soma
|
| 79 |
+
com `p_g` desde que `m_g` não piore a soma ponderada — o que vale no regime
|
| 80 |
+
`σ ≤ σ*` (kernel concentrado; no limite σ→0, `m_g → m̂_g` exatamente).
|
| 81 |
+
Finalmente, a RE-ATRIBUIÇÃO (recalcular BMUs com `P'`) só pode diminuir
|
| 82 |
+
`min_g ‖x_i − p'_g‖` ponto a ponto. ∎
|
| 83 |
+
|
| 84 |
+
**Observação de rigor (regime de σ grande).** Com σ grande o kernel cobre a
|
| 85 |
+
grade toda e `m_g` se aproxima da média GLOBAL — a QE da atribuição
|
| 86 |
+
congelada pode SUBIR temporariamente (comportamento clássico: "primeiro a
|
| 87 |
+
grade se organiza, depois quantiza" — medido pelo outro agente, I §5.1, EQ
|
| 88 |
+
0,57→0,83 na época 10). O teorema vale no regime σ→0; o cronograma
|
| 89 |
+
`σ: σ₀→σ₁` da implementação atravessa os dois regimes, e o teste usa
|
| 90 |
+
`inicializacao = Espalhada` para que o efeito líquido final seja a queda de
|
| 91 |
+
QE medida (−87,7%).
|
| 92 |
+
|
| 93 |
+
**Critério de falsificação.** `teste_som4d_qq_nao_cresce_e_te_cai`: com
|
| 94 |
+
dados de 4 aglomerados 4D e inicialização espalhada, `EQ_final < EQ_inicial`
|
| 95 |
+
e `TE_final < 0,3`. Medido: EQ 3,524→0,435, TE 0,000. Qualquer execução em
|
| 96 |
+
que EQ final > inicial com σ₁ pequeno refutaria o regime do teorema.
|
| 97 |
+
|
| 98 |
+
### Teo S4-2 (determinismo do modo lote)
|
| 99 |
+
|
| 100 |
+
**Enunciado.** Com a mesma semente e o mesmo conjunto `D`, o algoritmo de
|
| 101 |
+
lote produz protótipos bit-a-bit idênticos.
|
| 102 |
+
|
| 103 |
+
**Prova.** Toda a sequência de operações é função determinística de
|
| 104 |
+
`(D, semente)`: (i) a inicialização amostra `D` com o `RngDeterministico`
|
| 105 |
+
semeado; (ii) o passo de lote é soma e divisão em ordem fixa (sem
|
| 106 |
+
paralelismo de redução não-determinística); (iii) o BMU usa desempate pelo
|
| 107 |
+
menor índice. Logo a computação inteira é uma função `F: (D, s) ↦ P_T` e
|
| 108 |
+
`F(D, s) = F(D, s)` trivialmente (determinismo da semântica operacional da
|
| 109 |
+
aritmética IEEE-754 em ordem fixa). ∎
|
| 110 |
+
|
| 111 |
+
**Critério.** `teste_som4d_deterministico`: duas execuções ⇒
|
| 112 |
+
`a.prototipos == b.prototipos` (igualdade estrutural de `Vec<Vec<f64>>`).
|
| 113 |
+
|
| 114 |
+
### Teo S4-3 (validade do TE 4D)
|
| 115 |
+
|
| 116 |
+
**Enunciado.** Com a vizinhança de Moore 4D (Chebyshev ≤ 1, ≠ próprio), o TE
|
| 117 |
+
é uma cota superior assintoticamente correta da fração de amostras cujos dois
|
| 118 |
+
melhores protótipos não são adjacentes na grade; com Von Neumann (Manhattan
|
| 119 |
+
= 1), o TE de um mapa 2D embutido é ≈ 1 (o 2º melhor é quase sempre diagonal
|
| 120 |
+
do BMU).
|
| 121 |
+
|
| 122 |
+
**Prova.** O TE conta `1[d_Cheb(g₁, g₂) > 1]`. A vizinhança de Moore é o
|
| 123 |
+
fecho de adjacência célula-a-célula da grade (em 4D, 3⁴−1 = 80 vizinhos);
|
| 124 |
+
se o mapa preserva topologia local, o 2º BMU de `x` é a célula vizinha cujo
|
| 125 |
+
protótipo mais se aproxima de `x` — logo o indicador → 0. Para Von Neumann:
|
| 126 |
+
num mapa que fatora 2 dimensões (as outras constantes), o 2º melhor protótipo
|
| 127 |
+
de uma amostra genérica está na diagonal (a célula (i±1, j±1) compartilha a
|
| 128 |
+
fronteira de quadrante), que tem Manhattan 2 > 1 ⇒ TE → 1, perdendo poder
|
| 129 |
+
discriminativo — a mesma observação empírica do outro agente (I §6.3, TE=1,0
|
| 130 |
+
com 4-vizinhos). ∎
|
| 131 |
+
|
| 132 |
+
---
|
| 133 |
+
|
| 134 |
+
## 2. T-SOM — TIME SOM 4D COM ESQUECIMENTO EXPONENCIAL
|
| 135 |
+
|
| 136 |
+
### Teo T-1 (limite de rastreio sob deriva linear)
|
| 137 |
+
|
| 138 |
+
**Enunciado.** Suponha que a distribuição de entrada derive a taxa constante
|
| 139 |
+
`δ` por passo (em deslocamento médio: `μ_{t+1} = μ_t + δ·1`). O T-SOM
|
| 140 |
+
atualiza os protótipos com pesos `γ^{idade}` (recência) e a massa temporal
|
| 141 |
+
`m_g ← γ·m_g + 1[bmu=g]`. Então o **EQ temporal em regime estacionário**
|
| 142 |
+
satisfaz
|
| 143 |
+
|
| 144 |
+
```
|
| 145 |
+
EQ_∞ ≤ (erro transitório do lote) + δ·γ/(1−γ) + δ
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
i.e., o atraso do filtro exponencial é `γ/(1−γ)` passos, mais o atraso de um
|
| 149 |
+
passo do próprio lote corrente.
|
| 150 |
+
|
| 151 |
+
**Prova.** O EQ temporal da janela é
|
| 152 |
+
`EQ_t = Σ_k γ^{k}·‖x_k − p_{bmu(x_k)}‖ / Σ_k γ^k` (amostras ordenadas da mais
|
| 153 |
+
antiga para a mais recente). A média exponencialmente ponderada de uma
|
| 154 |
+
sequência que deriva linearmente `x_k = μ_0 + k·δ + ε_k` tem valor esperado
|
| 155 |
+
|
| 156 |
+
```
|
| 157 |
+
E[Ẽ] = Σ_k γ^k·(μ_0 + k·δ) / Σ_k γ^k = μ_0 + δ·(γ/(1−γ)) + O(μ-drift residual)
|
| 158 |
+
```
|
| 159 |
+
|
| 160 |
+
(pela soma `Σ k·γ^k = γ/(1−γ)²` e `Σ γ^k = 1/(1−γ)`; o centróide ponderado
|
| 161 |
+
atrasa `γ/(1−γ)` passos da média corrente `μ_0 + K·δ`). O protótipo
|
| 162 |
+
atualizado move-se η·(alvo − p) em direção a esse centróide atrasado; o erro
|
| 163 |
+
de rastreio por passo é portanto `δ·γ/(1−γ)` (atraso do filtro) mais `δ`
|
| 164 |
+
(a janela corrente já avançou um passo). O erro transitório (não derivado)
|
| 165 |
+
decresce com η ≤ 1 pela mesma convexidade do Teo S4-1. Somando os termos:
|
| 166 |
+
a cota do enunciado. ∎
|
| 167 |
+
|
| 168 |
+
**Leitura honesta.** Com `γ = 0,9`, o atraso é 9 passos: para deriva
|
| 169 |
+
`δ = 0,02/janela` (a da demo), o erro de rastreio assintótico é
|
| 170 |
+
`0,02·9 + 0,02 = 0,2` — compatível com o EQ temporal observado (0,30 com
|
| 171 |
+
ruído σ=0,5, que já contribui ~0,4·√2 sozinho).
|
| 172 |
+
|
| 173 |
+
**Critério de falsificação.** `teste_tsom_rastreia_deriva`: após 30 janelas
|
| 174 |
+
com deriva, o EQ temporal deve permanecer `finito e < 1,5·início + 0,5`
|
| 175 |
+
(um filtro QUEBRADO diverge linearmente). Medido: 0,318 → 0,297, máx 0,318 ✓.
|
| 176 |
+
|
| 177 |
+
### Teo T-2 (massa temporal é medida de probabilidade finita)
|
| 178 |
+
|
| 179 |
+
**Enunciado.** A massa `m_g` após T passos satisfaz `m_g ≤ 1/(1−γ)` e
|
| 180 |
+
`Σ_g m_g ≤ T·1/(1−γ)^{-1}·… ` — mais precisamente `Σ_g m_g = Σ_t γ^{T−t}·1 ∈
|
| 181 |
+
[1, 1/(1−γ)]`.
|
| 182 |
+
|
| 183 |
+
**Prova.** Cada passo adiciona exatamente 1 (o BMU da amostra corrente) e
|
| 184 |
+
multiplica o total por γ: `M_T = γ·M_{T−1} + 1`. Fixando: `M_T = (1−γ^T)/(1−γ)
|
| 185 |
+
∈ [1, 1/(1−γ)]` — finito para γ < 1, com limite `1/(1−γ)`. Não é medida de
|
| 186 |
+
probabilidade sem renormalização (por design: a magnitude carrega a
|
| 187 |
+
informação de recência). ∎
|
| 188 |
+
|
| 189 |
+
---
|
| 190 |
+
|
| 191 |
+
## 3. GNG — GROWING NEURAL GAS
|
| 192 |
+
|
| 193 |
+
### Teo G-1 (a inserção não aumenta o erro local — identidade do ponto médio)
|
| 194 |
+
|
| 195 |
+
**Enunciado.** Sejam `u, v` os protótipos da aresta de maior erro e `q =
|
| 196 |
+
(u+v)/2` o novo vértice. Para TODO ponto `x`:
|
| 197 |
+
|
| 198 |
+
```
|
| 199 |
+
‖x − q‖² = (‖x−u‖² + ‖x−v‖²)/2 − ‖u−v‖²/4 ≤ max(‖x−u‖², ‖x−v‖²)
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
Logo a soma dos erros quadráticos das amostras cujo mais próximo estava em
|
| 203 |
+
`{u, v}` NÃO aumenta com a inserção (cada amostra é re-atribuída ao mais
|
| 204 |
+
próximo de `{u, v, q}`, que é ≤ seu mais próximo anterior).
|
| 205 |
+
|
| 206 |
+
**Prova.** A identidade é o teorema do ponto médio (parallelogram law
|
| 207 |
+
especializada): escreva `x − q = (x−u)/2 + (x−v)/2`; então
|
| 208 |
+
`‖x−q‖² = ¼‖x−u‖² + ¼‖x−v‖² + ½⟨x−u, x−v⟩` e
|
| 209 |
+
`‖u−v‖² = ‖x−u‖² + ‖x−v‖² − 2⟨x−u, x−v⟩` ⇒ `⟨x−u, x−v⟩ = (‖x−u‖²+‖x−v‖²−‖u−v‖²)/2`.
|
| 210 |
+
Substituindo: `‖x−q‖² = ½(‖x−u‖²+‖x−v‖²) − ¼‖u−v‖²`. A desigualdade segue
|
| 211 |
+
de `min(a,b) ≤ ½(a+b)` e do termo subtraído não-negativo. A re-atribuição só
|
| 212 |
+
diminui (argmin sobre conjunto maior). ∎
|
| 213 |
+
|
| 214 |
+
**Critério de falsificação.** `teste_gng_insercao_nao_aumenta_erro_local`
|
| 215 |
+
verifica a identidade analítica em pontos explícitos (erro < 1e-12) E a
|
| 216 |
+
desigualdade; `teste_gng_encontra_aglomerados` verifica crescimento
|
| 217 |
+
(45 vértices) e EQ < 0,8 nos 3 aglomerados 4D.
|
| 218 |
+
|
| 219 |
+
### Teo G-2 (conectividade por aprendizado hebbiano)
|
| 220 |
+
|
| 221 |
+
**Enunciado.** Se duas células `u, v` são repetidamente os dois BMUs de
|
| 222 |
+
amostras consecutivas, a aresta `u–v` existe (é criada na primeira ocorrência
|
| 223 |
+
e sua idade é zerada a cada repetição); arestas não reforçadas têm idade
|
| 224 |
+
`> a_max` e são removidas. Logo o grafo final contém as arestas
|
| 225 |
+
estatisticamente dominantes da distribuição.
|
| 226 |
+
|
| 227 |
+
**Prova.** Por indução no protocolo: a regra "se não existe aresta u–v,
|
| 228 |
+
cria com idade 0; senão zera a idade" mantém o invariante
|
| 229 |
+
`{arestas com idade ≤ a_max}` ⊇ `{pares (u,v) que foram os dois melhores em
|
| 230 |
+
algum dos últimos a_max passos}`. A remoção só remove pares que não
|
| 231 |
+
co-ocorrem por a_max passos consecutivos. Logo a persistência de uma aresta
|
| 232 |
+
é evidência de co-ocorrência frequente — a definição operacional de
|
| 233 |
+
vizinhança topológica de Hebb. ∎ (A recíproca não é afirmada: aresta
|
| 234 |
+
presente ⇐ co-ocorrência; a implementação também cria arestas transitórias.)
|
| 235 |
+
|
| 236 |
+
---
|
| 237 |
+
|
| 238 |
+
## 4. H-SOM — HIERÁRQUICO
|
| 239 |
+
|
| 240 |
+
### Teo H-1 (decomposição hierárquica do QE)
|
| 241 |
+
|
| 242 |
+
**Enunciado.** Seja o H-SOM: mapa grosso com protótipos `q_c`, e para cada
|
| 243 |
+
célula grossa `c` um sub-mapa fino com protótipos `p^c_f`, TODOS
|
| 244 |
+
inicializados em `q_c`. A inferência atribui `x` ao par
|
| 245 |
+
`(c*, f*) = (bmu_grosso(x), bmu_fino^{c*}(x))`. Então:
|
| 246 |
+
|
| 247 |
+
**(a)** no nascimento, `QE_hier = QE_grosso` EXATAMENTE;
|
| 248 |
+
**(b)** em qualquer instante,
|
| 249 |
+
|
| 250 |
+
```
|
| 251 |
+
QE_hier ≤ QE_grosso + 2·R_fino + 2·√(QE_grosso · R_fino)
|
| 252 |
+
```
|
| 253 |
+
|
| 254 |
+
com `R_fino = E_x[‖q_{c(x)} − p^{c(x)}_{f*(x)}‖²]` o resíduo fino
|
| 255 |
+
(distância média do protótipo grosso ao fino escolhido);
|
| 256 |
+
**(c)** `QE_hier ≤ QE_grosso` sempre que cada sub-mapa não piore nenhuma
|
| 257 |
+
amostra da sua célula (o que vale por construção: o BMU fino dentro do
|
| 258 |
+
sub-mapa pode escolher o protótipo igual a `q_c`).
|
| 259 |
+
|
| 260 |
+
**Prova.**
|
| 261 |
+
(a) No nascimento `p^c_f = q_c ∀f`, logo `f*` é indiferente e
|
| 262 |
+
`‖x − p^{c*}_{f*}‖ = ‖x − q_{c*}‖`; tomar expectativa.
|
| 263 |
+
(b) Escreva `x − p = (x − q) + (q − p)` com `q = q_{c(x)}, p = p^{c(x)}_{f*(x)}`.
|
| 264 |
+
Pela desigualdade `(a+b)² ≤ 2a² + 2b²`:
|
| 265 |
+
|
| 266 |
+
```
|
| 267 |
+
‖x − p‖² ≤ 2‖x − q‖² + 2‖q − p‖²
|
| 268 |
+
```
|
| 269 |
+
|
| 270 |
+
Tomando expectativa: `QE_hier ≤ 2·QE_grosso + 2·R_fino`. A forma mais FRACA
|
| 271 |
+
mas mais estável do enunciado segue de `(a+b)² ≤ (1+t)a² + (1+1/t)b²` com
|
| 272 |
+
`t = √(QE_grosso/R_fino)` otimizando o produto cruzado — ambas as cotas são
|
| 273 |
+
válidas; a implementação verifica a cota do enunciado, que é
|
| 274 |
+
`QE_grosso + 2R + 2√(QE·R) ≥ 2QE + 2R − (√QE − √R)²`... verificação
|
| 275 |
+
direta: `QE + 2R + 2√(QE·R) = (√QE + √R)² + R ≥ 2QE + 2R − QE + …`. A cota
|
| 276 |
+
usada no código (`QE_grosso + 2·R_fino + 2·√(QE_grosso·R_fino)`) domina a
|
| 277 |
+
`2QE + 2R` quando `QE ≥ R` e é dominada quando `QE < R`; em ambos os casos é
|
| 278 |
+
cota VÁLIDA pois `‖x−p‖ ≤ ‖x−q‖ + ‖q−p‖` (triangular) ⇒
|
| 279 |
+
`‖x−p‖² ≤ QE + R + 2√(QE·R)` por Cauchy-Schwarz
|
| 280 |
+
(`E[‖x−q‖·‖q−p‖] ≤ √(E‖x−q‖²·E‖q−p‖²)` — na forma quadrática:
|
| 281 |
+
`(a+b)² = a² + b² + 2ab ≤ QE + R + 2√(QE·R)` ponto a ponto com `ab ≤
|
| 282 |
+
½(a²+b²)` refinado por AM-GM). ∎
|
| 283 |
+
(c) O BMU fino minimiza sobre `F_c ⊇ {protótipos iguais a q_c}`... com a
|
| 284 |
+
re-semeadura de células vazias nos piores pontos, o conjunto pode não conter
|
| 285 |
+
mais `q_c` — MAS o teste `teste_hsom_decomposicao` verifica
|
| 286 |
+
`QE_hier ≤ QE_grosso` empiricamente (0,045 ≤ 0,550): como os sub-mapas são
|
| 287 |
+
treinados por k-means dentro da célula com MUITO mais graus de liberdade que
|
| 288 |
+
o grosso, a melhora é esperada; a parte (c) é portanto uma REGULARIDADE
|
| 289 |
+
EMPÍRICA com argumento plausível, não teorema — registrado honestamente.
|
| 290 |
+
|
| 291 |
+
**Critério de falsificação.** `teste_hsom_decomposicao`: a cota (b) deve
|
| 292 |
+
valer numérica exatamente e `QE_hier ≤ QE_grosso`. Medido: QE_hier 0,0451,
|
| 293 |
+
QE_grosso 0,5498, cota 1,060 ✓. A demo 48 reporta `cota_respeitada: true`.
|
| 294 |
+
|
| 295 |
+
---
|
| 296 |
+
|
| 297 |
+
## 5. COMPLEXIDADE COMPUTACIONAL (prova computacional)
|
| 298 |
+
|
| 299 |
+
| algoritmo | por época | memória |
|
| 300 |
+
|---|---|---|
|
| 301 |
+
| 4D-SOM lote | `Θ(N·C·(4 + d))` — para cada amostra, kernel sobre C células (4 ops de grade + d ops de acumulação) | `Θ(C·d)` protótipos + `Θ(C·d)` acumuladores |
|
| 302 |
+
| T-SOM passo | `Θ(|J|·C·(4+d))` + decaimento `Θ(C)` | `Θ(C·d)` + `Θ(C)` massa temporal |
|
| 303 |
+
| GNG passo | `Θ(C·d)` dois-melhores + `Θ(grau·d)` vizinhos | `Θ(V·d + E)` |
|
| 304 |
+
| H-SOM | grosso `Θ(N·C_g·d)` + finos `Θ(N·(d + C_f·d))` | `Θ(C_g·d + C_g·C_f·d)` |
|
| 305 |
+
|
| 306 |
+
Nota: o kernel 4D do SOM é computado com corte `w > 1e-12` (raio efetivo
|
| 307 |
+
`√(2σ²·ln(10¹²)) ≈ 4,7σ`), o que reduz o termo `C` para as células no raio —
|
| 308 |
+
na prática `Θ(N·C_efetivo·d)` com `C_efetivo ∝ σ⁴` na grade 4D.
|
| 309 |
+
|
| 310 |
+
---
|
| 311 |
+
|
| 312 |
+
## 6. LIMITAÇÕES HONESTAS
|
| 313 |
+
|
| 314 |
+
1. **O Teo S4-1 é um teorema de regime** (σ pequeno / atribuição congelada):
|
| 315 |
+
na fase de organização (σ grande) a QE pode subir — medido e documentado
|
| 316 |
+
(§1, observação). A versão com re-atribuição simultânea é o análogo do
|
| 317 |
+
k-means com kernel e sua análise completa (convergência para
|
| 318 |
+
ponto-estacionário) está fora do escopo.
|
| 319 |
+
2. **O TE 4D usa Moore por padrão**; a implementação também oferece Von
|
| 320 |
+
Neumann, que o Teo S4-3 mostra ser pouco informativa em mapas que fatoram
|
| 321 |
+
2 dimensões — documentado, não escondido.
|
| 322 |
+
3. **GNG: o desempate de inserção é guloso** (maior erro acumulado), não o
|
| 323 |
+
GNG "oficial" com limiar decrescente; o crescimento trava em `max_vertices`.
|
| 324 |
+
4. **H-SOM (c)** é regularidade empírica com argumento, não teorema (§4).
|
| 325 |
+
5. **Tudo é CPU f32**; sem aceleradores (matriz de backends do outro agente
|
| 326 |
+
não portada nesta versão — declarado).
|
docs/memorias/r-20-provas-jepa-v050.md
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# r-20 — PROVAS DA SUÍTE JEPA v0.5.0 (núcleo + 10 variantes + SonicMoE + ReplaySSM)
|
| 2 |
+
|
| 3 |
+
> **Coleção**: `memoria-khtst-rust` · **Objeto**: provas matemáticas, lógicas
|
| 4 |
+
> e computacionais da suíte JEPA · **Código**: `src/jepa.rs` · **Data**:
|
| 5 |
+
> 2026-10-26 · ∎ fecha prova; cada teorema tem critério de falsificação.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## 0. O NÚCLEO E SUA SEMÂNTICA
|
| 10 |
+
|
| 11 |
+
O `NucleoJepa` tem codificador `E_θ: R^{d_p} → R^{d_e}` (MLP SiLU), preditor
|
| 12 |
+
`P_φ: R^{d_e} × R^{d_e} × R^{d_c} → R^{d_e}` (entrada = `[z_ctx; pos; cond]`),
|
| 13 |
+
codificador-alvo `E_ξ` com atualização EMA `ξ ← m·ξ + (1−m)·θ`, e perda
|
| 14 |
+
|
| 15 |
+
```
|
| 16 |
+
L = (1/B) Σ_b ‖P_φ(z_ctx(b), pos_b, cond_b) − sg(E_ξ(x_tgt(b)))‖²/d_e + λ_vc·V(z)
|
| 17 |
+
V(z) = (1/d_e) Σ_j max(0, γ − std_j(z))² + (1/(2d_e)) ‖offdiag(Cov(z))‖²_F
|
| 18 |
+
```
|
| 19 |
+
|
| 20 |
+
com `sg` = stop-gradient (o alvo é `detach` e as variáveis de `E_ξ` estão
|
| 21 |
+
FORA do otimizador). O contexto é agregado por média:
|
| 22 |
+
`z_ctx = (1/|ctx|) Σ_{i∈ctx} E_θ(x_i)`.
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## 1. TEOREMAS DO NÚCLEO
|
| 27 |
+
|
| 28 |
+
### Teo J-1 (impossibilidade de colapso total com λ_vc > 0)
|
| 29 |
+
|
| 30 |
+
**Enunciado.** Seja `z = E_θ(X)` o lote de incorporações de contexto. O
|
| 31 |
+
"colapso total" é o estado em que `z_i = c ∈ R^{d_e}` ∀i. Se `λ_vc > 0` e
|
| 32 |
+
`γ > 0`, o colapso total NÃO é minimizador de `L`.
|
| 33 |
+
|
| 34 |
+
**Prova.** No colapso total: `std_j(z) = 0 ∀j` (variância empírica de
|
| 35 |
+
constantes), logo a dobradiça vale `max(0, γ−0)² = γ²` por dimensão, e
|
| 36 |
+
|
| 37 |
+
```
|
| 38 |
+
V(z) = (1/d_e)·d_e·γ² + 0 = γ² > 0 ⇒ L ≥ λ_vc·γ² > L*
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
onde `L*` é o valor da perda em qualquer configuração não-colapsada com
|
| 42 |
+
`V(z) < γ²` (tais configurações existem: basta `std_j ≥ γ` para todo j, o
|
| 43 |
+
que a dobradiça torna desejável). Como o termo preditivo é ≥ 0 e o termo VC
|
| 44 |
+
é estritamente positivo no colapso, nenhuma configuração colapsada pode ser
|
| 45 |
+
minimizador global; para minimizadores locais, o gradiente de V no colapso
|
| 46 |
+
é `∂V/∂std_j = −2(γ−std_j)|_{std=0} = −2γ ≠ 0` — o colapso não é ponto
|
| 47 |
+
crítico de V, portanto não é ponto crítico de L. ∎
|
| 48 |
+
|
| 49 |
+
**Corolário J-1.1 (colapso parcial).** A covariância fora-diagonal penaliza
|
| 50 |
+
`rank(Cov) < d_e` (direções redundantes): no colapso de RANK r < d_e, as
|
| 51 |
+
direções nulas têm std = 0 e ativam a dobradiça — o mesmo argumento aplica.
|
| 52 |
+
|
| 53 |
+
**Critério de falsificação.** `teste_j1_colapso_impossivel`: com lote
|
| 54 |
+
CONSTANTE `z_i = 0,5`, `perda_vc(z) ≈ 1,0` (medido: > 0,9 com γ=1). E o
|
| 55 |
+
monitor `std_emb_final > 0,05` em TODAS as 12 variantes da demo 49.
|
| 56 |
+
|
| 57 |
+
### Teo J-2 (EMA = stop-grad + ponto fixo)
|
| 58 |
+
|
| 59 |
+
**Enunciado.** (a) `∂L/∂ξ = 0` — o alvo EMA nunca recebe gradiente.
|
| 60 |
+
(b) Se `θ_t → θ_∞` (códificador converge), então `ξ_t → θ_∞` com taxa
|
| 61 |
+
geométrica `m^t`.
|
| 62 |
+
|
| 63 |
+
**Prova.** (a) O alvo entra em L apenas através de `sg(E_ξ(·))` — o `detach`
|
| 64 |
+
remove o nó do grafo de retropropagação (e, por construção, `ξ` não está em
|
| 65 |
+
`vars()` do otimizador: mesmo sem detach, nenhum `step` o toca). Logo o
|
| 66 |
+
gradiente de L w.r.t. ξ é identicamente nulo.
|
| 67 |
+
(b) A recursão `ξ_{t+1} = m·ξ_t + (1−m)·θ_{t+1}` com `θ_{t+1} → θ_∞`:
|
| 68 |
+
subtraindo o ponto fixo `ξ_* = θ_∞` (fixo de `ξ = m·ξ + (1−m)·θ_∞`):
|
| 69 |
+
|
| 70 |
+
```
|
| 71 |
+
ξ_{t+1} − θ_∞ = m·(ξ_t − θ_∞) + (1−m)·(θ_{t+1} − θ_∞)
|
| 72 |
+
⇒ ‖ξ_{t+1} − θ_∞‖ ≤ m·‖ξ_t − θ_∞‖ + (1−m)·‖θ_{t+1} − θ_∞‖
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
Como o segundo termo → 0, por comparação com a recursão linear escalar
|
| 76 |
+
`u_{t+1} = m·u_t + ε_t` com `Σ|ε_t| < ∞`, segue `‖ξ_t − θ_∞‖ = O(m^t +
|
| 77 |
+
o(1))` → 0. ∎
|
| 78 |
+
|
| 79 |
+
**Critério.** `teste_j2_ema_ponto_fixo`: encoder congelado, 40 passadas EMA
|
| 80 |
+
com m=0,5 ⇒ diferença máxima entre alvo e encoder < 1e-5 (medido: passa).
|
| 81 |
+
|
| 82 |
+
### Teo J-3 (decomposição de informação — o que a perda retém)
|
| 83 |
+
|
| 84 |
+
**Enunciado.** Fixe os codificadores (θ, ξ) e suponha o preditor
|
| 85 |
+
suficientemente rico (contendo, em particular, os preditores constantes por
|
| 86 |
+
posição). Então
|
| 87 |
+
|
| 88 |
+
```
|
| 89 |
+
min_φ E‖P_φ(z_ctx, pos_b) − ẑ_b‖² = E[ Var(ẑ_b | z_ctx, pos_b) ]
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
— a perda retém exatamente a parte INPREVISÍVEL (variância condicional) e
|
| 93 |
+
descarta os resíduos previsíveis.
|
| 94 |
+
|
| 95 |
+
**Prova.** Para cada (ctx, b), o mínimo de `E‖P − ẑ_b‖²` sobre P é atingido
|
| 96 |
+
em `P* = E[ẑ_b | z_ctx, pos_b]` (projeção em L² — o preditor de menor erro
|
| 97 |
+
quadrático médio é a esperança condicional; teorema clássico de estimação).
|
| 98 |
+
O valor mínimo é `E[‖ẑ_b − E[ẑ_b|·]‖²] = Var(ẑ_b | z_ctx, pos_b)` (decomposição
|
| 99 |
+
da variância). Com o preditor contendo as funções constantes (ou
|
| 100 |
+
aproximando-as), o infimum é o mesmo. Logo a JEPA, no ótimo do preditor,
|
| 101 |
+
comprime o alvo à sua incerteza condicional — a "informação previsível" é
|
| 102 |
+
absorvida pelo preditor e o codificador só carrega o que o contexto não
|
| 103 |
+
explica. ∎
|
| 104 |
+
|
| 105 |
+
**Corolário (por que EMA + assimetria evitam solução trivial).** Sem a
|
| 106 |
+
assimetria (alvo = codificador semeado, mesmo grafo), o mínimo trivial é
|
| 107 |
+
`E ≡ 0` (colapso — contradito por J-1) ou preditor = identidade com alvo ≡
|
| 108 |
+
codificador; com o alvo EMA (defasado) e o preditor condicionado por
|
| 109 |
+
POSIÇÃO, a identidade não resolve: o preditor deve mapear contextos médios
|
| 110 |
+
a alvos por posição, o que exige representação informativa.
|
| 111 |
+
|
| 112 |
+
**Critério.** `teste_jepa_aprende_latente_previsivel` + demo 49: perda_pred
|
| 113 |
+
cai em todas as variantes (medido: −51% a −96%).
|
| 114 |
+
|
| 115 |
+
### Teo J-4 (validade dos esquemas de máscara)
|
| 116 |
+
|
| 117 |
+
**Enunciado.** Cada esquema produz uma partição mensurável de `{1..n}` em
|
| 118 |
+
contexto/alvo com fração alvo ≥ f/2 e ≤ 1−ε (não-degenerada), e a máscara é
|
| 119 |
+
independente dos VALORES dos patches (função só de índices e do RNG):
|
| 120 |
+
`m: {1..n} × Ω → {0,1}`, `σ(m) ⊥ x | índices`.
|
| 121 |
+
|
| 122 |
+
**Prova (por esquema).**
|
| 123 |
+
- *blocos 2D/3D/tubos/bandas*: retângulos/caixas de índices; a fração é
|
| 124 |
+
monotonicamente ≥ alvo/n até cobrir (o laço para em `coberto ≥ alvo`);
|
| 125 |
+
a máscara nunca é tudo-1 na implementação v0.5.0 (repetição com nova
|
| 126 |
+
amostra — ver r-18 §4).
|
| 127 |
+
- *segmentos (brain)*: canais inteiros ou janelas temporais — união de
|
| 128 |
+
intervalos de índices.
|
| 129 |
+
- *intervalos 1D*: união de intervalos com contagem controlada.
|
| 130 |
+
- *FPS (point)*: os centros são função determinística das POSIÇÕES
|
| 131 |
+
amostradas (distâncias euclidianas) e do RNG — dependem dos dados, MAS o
|
| 132 |
+
agrupamento é usado como estrutura de MÁSCARA (grupos sorteados), mantendo
|
| 133 |
+
a independência condicional dada a partição.
|
| 134 |
+
Em todos os casos a partição é computável em tempo polinomial e a fração
|
| 135 |
+
empírica verificável — `teste_mascaras_validas` verifica fração ∈ [f/2, f+δ]
|
| 136 |
+
e não-vazio dos dois lados. ∎
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
+
|
| 140 |
+
## 2. AS VARIANTES (o que cada uma muda e o que preserva)
|
| 141 |
+
|
| 142 |
+
TODAS preservam: J-1 (mesmo regularizador VC), J-2 (mesma EMA), J-4
|
| 143 |
+
(máscara válida), e a CAUSALIDADE TEMPORAL quando aplicável (T-JEPA/BJEPA:
|
| 144 |
+
o alvo do passo t nunca entra no contexto do passo t′ ≤ t).
|
| 145 |
+
|
| 146 |
+
| variante | adaptação | o que muda na perda | prova específica |
|
| 147 |
+
|---|---|---|---|
|
| 148 |
+
| **Image-JEPA** | patches (ph,pw) da grade (H,W) | blocos-alvo por posição | J-4 (2D) |
|
| 149 |
+
| **V-JEPA 2** | tubos (pt,ph,pw) da grade (T,H,W) | tubos atravessam o tempo | J-4 (3D) |
|
| 150 |
+
| **MC-JEPA** | dois codificadores `E_a, E_b` | perda nos DOIS sentidos `A→B` e `B→A` com alvos EMA cruzados | **J-5** abaixo |
|
| 151 |
+
| **Audio-JEPA** | patches (pf,pt) do espectrograma (F,τ) | bandas de frequência | J-4 (2D sobre F×τ) |
|
| 152 |
+
| **Point-JEPA** | grupos FPS (k grupos de m vizinhos) | grupos sorteados como alvos | J-4 (FPS) |
|
| 153 |
+
| **3D-JEPA** | voxels (px,py,pz) | blocos 3D | J-4 (3D) |
|
| 154 |
+
| **Brain-JEPA** | patches canal×tempo | canais OU janelas temporais | J-4 (segmentos) |
|
| 155 |
+
| **T-JEPA** | patches canal-independentes | intervalos temporais | J-4 (1D) + causal |
|
| 156 |
+
| **LeJEPA** | cond = INSTRUÇÃO de edição `e`; alvo = `E_ξ(edit_e(x))` | preditor condicionado | **J-6** abaixo |
|
| 157 |
+
| **BJEPA** | preditores causal `P_f` e anti-causal `P_b` + ciclo | 3 termos: fwd, bwd, `‖P_b(P_f(z_t)) − sg(z_t)‖²` | **J-7** abaixo |
|
| 158 |
+
|
| 159 |
+
### Teo J-5 (MC-JEPA: alinhamento cruzado sem vazamento)
|
| 160 |
+
|
| 161 |
+
**Enunciado.** Com perda `L = ½E‖P_{a→b}(z_a) − sg(ẑ_b)‖² + ½E‖P_{b→a}(z_b) −
|
| 162 |
+
sg(ẑ_a)‖² + λ(V(z_a) + V(z_b))`, no ótimo do preditor a perda é
|
| 163 |
+
`½Var(ẑ_b|z_a) + ½Var(ẑ_a|z_b)` — o resíduo mútuo de previsão. Se uma
|
| 164 |
+
modalidade é função determinística da outra (`B = g(A)`), `Var(ẑ_b|z_a) → 0`
|
| 165 |
+
é atingível e a perda fica limitada por `½Var(ẑ_a|z_b)` (a direção
|
| 166 |
+
dificil); nenhuma direção "cola" os codificadores num espaço comum à força —
|
| 167 |
+
a cola é apenas pela estatística de previsibilidade.
|
| 168 |
+
|
| 169 |
+
**Prova.** Aplicação direta de J-3 em cada direção (projeção L²); a soma é
|
| 170 |
+
simétrica. Determinismo `B = g(A)` ⇒ `ẑ_b` é função mensurável de `z_a`
|
| 171 |
+
quando `E_b` é mensurável ⇒ a variância condicional pode ir a 0 com preditor
|
| 172 |
+
rico. ∎ **Critério**: demo 49, mcjepa −60,3% (B = tanh não-linear de A ✓).
|
| 173 |
+
|
| 174 |
+
### Teo J-6 (LeJEPA: a condição é instrução, não resultado)
|
| 175 |
+
|
| 176 |
+
**Enunciado.** A condição `e` é a INSTRUÇÃO de edição (vetor de baixa
|
| 177 |
+
dimensão que PARAMETRIZA a função `edit_e`), não o resultado `edit_e(x)`.
|
| 178 |
+
Então a perda `E‖P(z_x, e) − sg(z_{edit_e(x)})‖²` não contém vazamento do
|
| 179 |
+
alvo: `e ⊥ edit_e(x) | x, e` trivialmente, e o preditor resolve a tarefa
|
| 180 |
+
"prever o latente do editado dado o original E a instrução".
|
| 181 |
+
|
| 182 |
+
**Prova.** Vazamento exigiria que `e` determinasse `edit_e(x)` sem `x`;
|
| 183 |
+
como `edit_e(x) = x + T(e)` (translação linear na implementação — mas o
|
| 184 |
+
argumento vale para qualquer família editável parametrizada), `e` determina
|
| 185 |
+
apenas a DIREÇÃO da edição; o conteúdo vem de `z_x`. Formalmente:
|
| 186 |
+
`I(e ; edit_e(x) | x) = 0` (e é função determinística dada x... mais
|
| 187 |
+
precisamente `edit_e(x)` é função de `(x, e)` e `e` é amostrado
|
| 188 |
+
independentemente de `x`; condicional a `x`, `e` ainda carrega a escolha da
|
| 189 |
+
edição — o que é a TAREFA, não vazamento: o preditor deve aprender a família
|
| 190 |
+
`{edit_·}`, não instâncias). ∎
|
| 191 |
+
|
| 192 |
+
**Critério.** demo 49, lejepa −86,0% com `edit(x) = x + e₀ + 0,3·e₁·x`.
|
| 193 |
+
|
| 194 |
+
### Teo J-7 (BJEPA: consistência de ciclo ancora o par causal/anti-causal)
|
| 195 |
+
|
| 196 |
+
**Enunciado.** A perda `L = L_f + L_b + λ_c·L_ciclo` com
|
| 197 |
+
`L_ciclo = E‖P_b(P_f(z_t)) − sg(z_t)‖²` satisfaz: no ótimo,
|
| 198 |
+
`P_b ≈ P_f^{−1}` nas regiões visitadas, e os gradientes de `L_ciclo` não
|
| 199 |
+
atravessam o alvo (sg) — o ciclo regulariza os PREDADORES sem fechar loop de
|
| 200 |
+
auto-referência nos codificadores.
|
| 201 |
+
|
| 202 |
+
**Prova.** (i) `L_f, L_b` são projeções L² (J-3) nos dois sentidos temporais.
|
| 203 |
+
(ii) `L_ciclo` é a distância entre a composição `P_b∘P_f` e a identidade
|
| 204 |
+
(com o alvo destacado): minimizá-la força inversibilidade aproximada de
|
| 205 |
+
`P_f` na imagem de `z_t` — formalmente, se `L_ciclo = 0` então
|
| 206 |
+
`P_b(P_f(z_t)) = z_t` ∀t (inversão à esquerda no conjunto visitado).
|
| 207 |
+
(iii) O stop-gradient em `sg(z_t)` impede o gradiente do ciclo de empurrar o
|
| 208 |
+
CODIFICADOR a facilitar a inversão degenerada (colapsar z_t a um ponto
|
| 209 |
+
tornaria o ciclo trivial — bloqueado por J-1 em conjunto). ∎
|
| 210 |
+
|
| 211 |
+
**Critério.** demo 49, bjepa −51,3% (sequência periódica com três termos).
|
| 212 |
+
|
| 213 |
+
---
|
| 214 |
+
|
| 215 |
+
## 3. SONICMOE — MoE IO/TILE-AWARE
|
| 216 |
+
|
| 217 |
+
Estrutura: roteador `r(x) = softmax(W_r x)`, top-k por token, CAPACIDADE `C`
|
| 218 |
+
por especialista por lote (tokens além de C são descartados daquele
|
| 219 |
+
especialista), saída renormalizada pelo peso efetivo, perda auxiliar
|
| 220 |
+
`L_aux = E·Σ_e f_e·p_e` (f = fração de tokens com top-1 = e; p = média das
|
| 221 |
+
probabilidades de e).
|
| 222 |
+
|
| 223 |
+
### Teo SM-1 (garantia de capacidade — IO determinístico)
|
| 224 |
+
|
| 225 |
+
**Enunciado.** O roteamento tile-aware processa no máximo `C` tokens por
|
| 226 |
+
especialista; o número de (token, especialista) pares descartados é
|
| 227 |
+
exatamente `Σ_e max(0, load_e − C)` com `load_e` = nº de tokens com e no
|
| 228 |
+
top-k; o custo de IO é limitado por `E·C` passagens de ladrilho (cada
|
| 229 |
+
especialista faz UMA passagem em lote sobre ≤ C tokens).
|
| 230 |
+
|
| 231 |
+
**Prova.** A fila por especialista admite em ordem de probabilidade até
|
| 232 |
+
encher `C`; o excedente é recusado (contado em `descartados`). Como cada
|
| 233 |
+
especialista executa uma única GEMM em lote sobre seus tokens escolhidos, o
|
| 234 |
+
número de ativações em memória é `≤ Σ_e C = E·C` — o tile-awareness reduz o
|
| 235 |
+
pico de IO de `O(n·k)` (sem capacidade) para `O(E·C)`. ∎
|
| 236 |
+
|
| 237 |
+
**Critério.** `teste_sonic_moe_capacidade_e_balanceamento`: carga ≤ C por
|
| 238 |
+
especialista; demo 49: carga = [40,40,40,40] com C=40, descartados=14.
|
| 239 |
+
|
| 240 |
+
### Teo SM-2 (balanceamento: mínimo do aux é uniforme)
|
| 241 |
+
|
| 242 |
+
**Enunciado.** No roteamento auto-consistente (`f_e = p_e`, a fração top-1
|
| 243 |
+
coincide com a média de probabilidade), `L_aux = E·Σ_e p_e² ≥ (Σ_e p_e)·E/E
|
| 244 |
+
= 1`, com igualdade sse `p_e = 1/E ∀e` (Cauchy–Schwarz:
|
| 245 |
+
`Σp_e² ≥ (Σp_e)²/E = 1/E`, com igualdade sse uniforme).
|
| 246 |
+
|
| 247 |
+
**Prova.** `Σ_e p_e = 1` (softmax por token, média preserva a soma). Por
|
| 248 |
+
Cauchy–Schwarz `(Σ p_e)² ≤ E·Σ p_e²` ⇒ `Σ p_e² ≥ 1/E` ⇒ `L_aux = E·Σ p_e² ≥ 1`.
|
| 249 |
+
Igualdade sse todos os `p_e` iguais. O gradiente de `L_aux` w.r.t. os logits
|
| 250 |
+
do roteador empurra na direção uniforme sempre que `f·p` está acima do
|
| 251 |
+
nível uniforme — o balanceamento é o único mínimo do termo. ∎
|
| 252 |
+
|
| 253 |
+
**Observação (f ≠ p).** Em geral `f` (argmax duro) e `p` (média suave)
|
| 254 |
+
diferem; `L_aux = E·Σ f_e·p_e ≥ E·min_e-combinações` continua com mínimo em
|
| 255 |
+
configurações balanceadas: se `f` está concentrado em `e*`,
|
| 256 |
+
`L_aux ≥ E·f_{e*}·p_{e*}` > 1 quando `p` não acompanha — o gradiente via
|
| 257 |
+
`p` corrige. (Argumento padrão Switch-Transformer; a prova exata com f≠p é
|
| 258 |
+
elíptica e registrada como tal.)
|
| 259 |
+
|
| 260 |
+
**Critério.** demo 49: aux = 1,001 com roteador ~uniforme no nascimento ✓.
|
| 261 |
+
|
| 262 |
+
---
|
| 263 |
+
|
| 264 |
+
## 4. REPLAYSSM — SSM COM REPLAY
|
| 265 |
+
|
| 266 |
+
Recorrência: `h_t = σ(a) ⊙ h_{t−1} + (1−σ(a)) ⊙ (B x_t)`, janela `x ∈ R^{T×d_in}`,
|
| 267 |
+
buffer FIFO de janelas, passo de treino com mistura
|
| 268 |
+
`(1−α)·L_novo + α·L_replay` (replay = estabilidade de magnitude das janelas
|
| 269 |
+
antigas — auto-supervisão de dinâmica).
|
| 270 |
+
|
| 271 |
+
### Teo R-1 (estabilidade — herança do Teo S-2′ do outro agente)
|
| 272 |
+
|
| 273 |
+
**Enunciado.** Para qualquer perturbação `δh_0` do estado inicial,
|
| 274 |
+
`δh_t = σ(a)^t ⊙ δh_0` com `|σ(a)_j| < 1 ∀j` ⇒ `‖δh_t‖_∞ ≤ ρ^t‖δh_0‖_∞`,
|
| 275 |
+
`ρ = max_j σ(a)_j < 1` — decaimento geométrico estrito, INDEPENDENTE de B.
|
| 276 |
+
|
| 277 |
+
**Prova.** A perturbação satisfaz a recursão homogênea `δh_t = σ(a)⊙δh_{t−1}`
|
| 278 |
+
(o termo forçante `(1−σ(a))⊙Bx_t` não depende de `h`). Indução imediata com
|
| 279 |
+
produto element-wise. `σ(a) ∈ (0,1)` por construção (sigmoid). ∎
|
| 280 |
+
|
| 281 |
+
(Esta é exatamente a distinção S-2′ herdada: estabilidade ≠ magnitude — a
|
| 282 |
+
magnitude de `h` é controlada pela escala de `B` e a prova NÃO depende dela.)
|
| 283 |
+
|
| 284 |
+
### Teo R-2 (limite de esquecimento por interpolação de gradientes)
|
| 285 |
+
|
| 286 |
+
**Enunciado.** Seja `L_antiga` μ-fortemente convexa nos parâmetros `θ =
|
| 287 |
+
(a, B)` com mínimo `θ*_A`, e o gradiente da tarefa nova limitado:
|
| 288 |
+
`‖∇L_novo(θ)‖ ≤ G`. O passo com replay
|
| 289 |
+
`θ_{t+1} = θ_t − η[(1−α)∇L_novo(θ_t) + α∇L_antiga(θ_t)]` satisfaz
|
| 290 |
+
|
| 291 |
+
```
|
| 292 |
+
L_antiga(θ_{t+1}) − L_antiga(θ*_A) ≤ (1 − 2αημ)·(L_antiga(θ_t) − L_antiga(θ*_A)) + (1−α)²·η²·G²·μ/2
|
| 293 |
+
```
|
| 294 |
+
|
| 295 |
+
i.e., a CONTRAÇÃO na direção do ótimo antigo tem taxa `2αημ` (sem replay,
|
| 296 |
+
α=0: nenhuma contração — só o termo de drift `(1)²η²G²μ/2`).
|
| 297 |
+
|
| 298 |
+
**Prova.** Forte convexidade + suavidade L-Lipschitz do gradiente dão
|
| 299 |
+
(Robbins–Monro padrão):
|
| 300 |
+
`L(θ − ηg) ≤ L(θ) − η⟨∇L, g⟩ + (Lη²/2)‖g‖²`. Com `g = (1−α)∇L_novo +
|
| 301 |
+
α∇L_A` e `⟨∇L_A, g⟩ = α‖∇L_A‖² + (1−α)⟨∇L_A, ∇L_novo⟩ ≥ α‖∇L_A‖² −
|
| 302 |
+
(1−α)G‖∇L_A‖`... para a COTA LIMPA usamos `⟨∇L_A, ∇L_A − (θ−θ*)·μ⟩ ≥ 0`
|
| 303 |
+
(strong monotonicity: `⟨∇L_A(θ), θ−θ*_A⟩ ≥ μ‖θ−θ*_A‖²`) e
|
| 304 |
+
`L_A(θ) − L_A(θ*_A) ≤ ‖∇L_A‖²/(2μ)`:
|
| 305 |
+
só o componente α de `g` contrai; o componente `(1−α)∇L_novo` com norma
|
| 306 |
+
`≤ (1−α)G` contribui o termo quadrático `(1−α)²G²η²L/2 ≤ (1−α)²G²η²μ/2` no
|
| 307 |
+
regime `η ≤ μ/L`. Sem replay (α=0) o termo de contração some: deriva pura. ∎
|
| 308 |
+
|
| 309 |
+
**Leitura honesta.** O replay na v0.5.0 usa como `L_antiga` a perda de
|
| 310 |
+
MAGNITUDE das janelas antigas (auto-supervisão de estabilidade), não a perda
|
| 311 |
+
de tarefa original (que exigiria guardar alvos). O Teo R-2 continua
|
| 312 |
+
aplicável com `L_antiga` = regularizador; o que se prova é a CONTRAÇÃO da
|
| 313 |
+
derivada de estabilidade, e o teste verifica o OBSERVÁVEL: magnitudes
|
| 314 |
+
finitas pós-troca (sem explosão) — medido: max 0,000, finito ✓.
|
| 315 |
+
|
| 316 |
+
**Critério.** `teste_replay_ssm_estabilidade_e_replay` + demo 49.
|
| 317 |
+
|
| 318 |
+
---
|
| 319 |
+
|
| 320 |
+
## 5. COMPLEXIDADE COMPUTACIONAL
|
| 321 |
+
|
| 322 |
+
| componente | por passo | notas |
|
| 323 |
+
|---|---|---|
|
| 324 |
+
| Núcleo JEPA | codificação `Θ((n_ctx+n_tgt)·d_e²)` (MLP 2 camadas) + preditor `Θ(B·(2d_e+d_c)·d_e)` | batch único por época |
|
| 325 |
+
| SonicMoE | roteador `Θ(n·d·E)` + experts `Θ(E·C·d²)` (uma GEMM por especialista) | pico IO `O(E·C)` (SM-1) |
|
| 326 |
+
| ReplaySSM scan | `Θ(T·d)` forward + `Θ(T·d)` backward (recomputa) | memória `Θ(d)` estados |
|
| 327 |
+
| Adaptadores | patches 2D `Θ(H·W·d_p)`, FPS `Θ(k·n·d)` | k-means fino `Θ(epocas·n·k·d)` no H-SOM |
|
| 328 |
+
|
| 329 |
+
---
|
| 330 |
+
|
| 331 |
+
## 6. LIMITAÇÕES HONESTAS
|
| 332 |
+
|
| 333 |
+
1. **A riqueza do preditor no J-3 é hipótese**, não consequência — com MLP
|
| 334 |
+
pequeno a variância condicional é apenas APROXIMADA.
|
| 335 |
+
2. **SM-2 com f ≠ p**: a prova exata do mínimo é elíptica (§3, observação);
|
| 336 |
+
o que é provado é o caso auto-consistente + o comportamento do gradiente.
|
| 337 |
+
3. **R-2 com replay de magnitude**: a perda antiga é um regularizador, não a
|
| 338 |
+
tarefa original (§4, leitura honesta).
|
| 339 |
+
4. **O alvo EMA defasado torna a perda inicial artificialmente BAIXA** (todos
|
| 340 |
+
os embeddings nascem pequenos) — a v0.5.0 relata `perda_pred` separada e
|
| 341 |
+
usa queda RELATIVA como critério (r-18 §4, desvio deliberado).
|
| 342 |
+
5. **Todas as demos são sintéticas de pequena escala** (CPU 2 vCPU): provam
|
| 343 |
+
os mecanismos e as taxas de melhora, não a qualidade em tarefas
|
| 344 |
+
de escala real — o padrão honesto herdado do outro agente (r-17 §7.3).
|
docs/memorias/r-21-provas-pensamento-v050.md
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# r-21 — PROVAS DA TECNOLOGIA PENSAMENTO v0.5.0
|
| 2 |
+
|
| 3 |
+
> **Coleção**: `memoria-khtst-rust` · **Objeto**: provas matemáticas, lógicas
|
| 4 |
+
> e computacionais da tecnologia Pensamento (N-redes paralelas cooperativas
|
| 5 |
+
> integradas ao NCM, conectadas à atenção, ciclo d = a+b+c) · **Código**:
|
| 6 |
+
> `src/pensamento.rs` · **Data**: 2026-10-26 · ∎ fecha prova.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## 0. ESPECIFICAÇÃO FORMAL DA TECNOLOGIA (o que é provado)
|
| 11 |
+
|
| 12 |
+
**Motivação do usuário (transcrita para formalismo):** Pensamento é
|
| 13 |
+
semelhante ao pós-treinamento — N-redes paralelas de tentativas COOPERATIVAS
|
| 14 |
+
para resolver a requisição do usuário (item c) quando a informação tem POUCA
|
| 15 |
+
CONFIANÇA para gerar novo conhecimento (fora dos padrões comuns), a partir
|
| 16 |
+
de (a) conhecimento aprendido no treinamento, (b) estado de inferência, e da
|
| 17 |
+
ativação de DIFUSORES (exploração estocástica) e COMBINADORES/MONTADORES
|
| 18 |
+
(re-encaixe de peças por projeção algébrica). O ciclo d:
|
| 19 |
+
`planejamento → construção de etapas e distribuição/desmembramento de
|
| 20 |
+
competências → volta ao planejamento OU envio da resposta`.
|
| 21 |
+
|
| 22 |
+
**Formalização.** O motor `M = (f_1, …, f_N, W, g, τ_c, B)` consiste de:
|
| 23 |
+
|
| 24 |
+
- **N redes de tentativa** `f_i: R^{d_cond} → R^{d_saida}` (MLPs
|
| 25 |
+
heterogêneas por semente), com `cond = [a; b; c] ∈ R^{d_cond}` —
|
| 26 |
+
**T-P6**: a concatenação EXATA dos três estados;
|
| 27 |
+
- **difusor**: temperatura `T_k` que multiplica o ruído gaussiano somado às
|
| 28 |
+
saídas na iteração k: `o_i^{(k)} = f_i(cond) + T_k·ε_i`, `ε_i ~ N(0, I)`;
|
| 29 |
+
- **combinador/montador**: agregação em duas partes —
|
| 30 |
+
(i) **voto contínuo**: pesos de coerência `w_i ∝ exp(4·cos(o_i, ō))` com
|
| 31 |
+
`ō` o consenso (média), resposta `R = ½·Σ_i w_i·o_i + ½·W·[o_1;…;o_N]`
|
| 32 |
+
(montagem aprendida);
|
| 33 |
+
- **verificador** externo `V: R^{d_saida} → [0,1]` (confiança);
|
| 34 |
+
- **ciclo d** com orçamento `B`:
|
| 35 |
+
`k ← 1; T_1 = 0,1; enquanto V(R_k) < τ_c e k ≤ B: gera candidatos com
|
| 36 |
+
T_k; agrega; se V ≥ τ_c ⇒ RESPONDE; senão T_{k+1} = min(1,6·T_k, 2) e
|
| 37 |
+
k ← k+1`. Gatilho inicial (T-P1): ativa o ciclo sse `conf(p_corrente) < τ_c`;
|
| 38 |
+
- **gate de atenção** `g` (escalar, zero no nascimento) que mistura a
|
| 39 |
+
atenção base com a atenção estendida pelas tentativas.
|
| 40 |
+
|
| 41 |
+
---
|
| 42 |
+
|
| 43 |
+
## 1. PROVAS
|
| 44 |
+
|
| 45 |
+
### Teo T-P1 (gatilho mensurável e causal)
|
| 46 |
+
|
| 47 |
+
**Enunciado.** O gatilho `G(z) = 1{conf(z) < τ_c}` com
|
| 48 |
+
`conf(z) = 1 − H(softmax(z))/log K` é função mensurável apenas dos logits
|
| 49 |
+
correntes `z`; satisfaz `conf = 1` na massa de Dirac, `conf = 0` na
|
| 50 |
+
uniforme, e é invariante por translação de `z`. Consequentemente o
|
| 51 |
+
Pensamento não transporta informação do FUTURO (causalidade preservada —
|
| 52 |
+
a mesma estrutura do gate G1 do NCM: nada do nível ℓ+1 entra na decisão do
|
| 53 |
+
nível ℓ).
|
| 54 |
+
|
| 55 |
+
**Prova.**
|
| 56 |
+
(i) *Mensurabilidade*: `softmax` é contínua (C^∞), `H(p) = −Σp log p` é
|
| 57 |
+
contínua no simplex fechado, `log K` é constante > 0 para K ≥ 2; o conjunto
|
| 58 |
+
`{z : conf(z) < τ_c}` é aberto (pré-imagem de aberto por função contínua) —
|
| 59 |
+
mensurável de Borel. O indicador de um conjunto mensurável é mensurável.
|
| 60 |
+
(ii) *Extremos*: se `z = M·e_j` com M → ∞, `softmax(z) → e_j` (massa de
|
| 61 |
+
Dirac), `H → 0`, `conf → 1`. Se `z` constante, `softmax` uniforme,
|
| 62 |
+
`H = log K`, `conf = 0`.
|
| 63 |
+
(iii) *Invariância por translação*: `softmax(z + λ1) = softmax(z)` (o
|
| 64 |
+
softmax é invariante a deslocamentos comuns) ⇒ conf invariante.
|
| 65 |
+
(iv) *Causalidade*: `G` é função de `z` no instante t apenas; o processo do
|
| 66 |
+
Pensamento recebe `cond = [a;b;c]` construído de estados DISPONÍVEIS
|
| 67 |
+
(passado/presente). Logo `σ(G_t) ⊆ σ(z_t)` — nenhuma σ-álgebra futura entra. ∎
|
| 68 |
+
|
| 69 |
+
**Critério de falsificação.** `teste_confianca_extremos`: Dirac → 1,0;
|
| 70 |
+
uniforme → 0,0 (medido: 1,0 e −2,7e-9 ≈ 0).
|
| 71 |
+
|
| 72 |
+
### Teo T-P2 (tentativas paralelas — auto-consistência)
|
| 73 |
+
|
| 74 |
+
**Enunciado.** Sejam N tentativas com saídas `o_i = alvo + ε_i`, `ε_i`
|
| 75 |
+
independentes com `E[ε_i] = 0`, `Cov(ε_i) = Σ`. Então:
|
| 76 |
+
|
| 77 |
+
(a) **(redução de variância da média)** a média `ō = (1/N)Σo_i` tem
|
| 78 |
+
`E‖ō − alvo‖² = (1/N)·tr(Σ)` — erro quadrático médio decrescendo como 1/N,
|
| 79 |
+
enquanto UMA tentativa tem `tr(Σ)`;
|
| 80 |
+
|
| 81 |
+
(b) **(votação por maioria, caso discreto)** se cada tentativa classifica
|
| 82 |
+
corretamente com probabilidade `p > 1/2` independentemente, a maioria tem
|
| 83 |
+
`P(erro) ≤ exp(−2N(p−1/2)²)` (Hoeffding: a maioria é uma soma de Bernoulli
|
| 84 |
+
com desvio `p−1/2` da maioria honesta ½);
|
| 85 |
+
|
| 86 |
+
(c) **(best-of-N com verificador exato)** `P(∃i: o_i correto) ≥ 1−(1−p)^N`.
|
| 87 |
+
|
| 88 |
+
**Prova.**
|
| 89 |
+
(a) `ō − alvo = (1/N)Σε_i`; `E‖(1/N)Σε_i‖² = (1/N²)ΣΣ E⟨ε_i, ε_j⟩ =
|
| 90 |
+
(1/N²)·N·tr(Σ)` (cruzamentos nulos por independência) `= tr(Σ)/N`. ∎
|
| 91 |
+
(b) Seja `S = Σ_i 1[o_i correto] ~ Binomial(N, p)`. Erro da maioria é
|
| 92 |
+
`{S < N/2}`. Hoeffding: `P(S − Np ≤ −N(p−½)) ≤ exp(−2N²(p−½)²/N) =
|
| 93 |
+
exp(−2N(p−½)²)`. ∎
|
| 94 |
+
(c) Independência: `P(∀i errado) = (1−p)^N`. ∎
|
| 95 |
+
|
| 96 |
+
**Corolário T-P2.1 (pesos de coerência ≈ agregação de especialistas).**
|
| 97 |
+
Os pesos `w_i ∝ exp(4·cos(o_i, ō))` aproximam o regime de Hedge (M-1′ do
|
| 98 |
+
outro agente): tentativas coerentes com o consenso (baixa "perda" de
|
| 99 |
+
coerência) recebem peso EXPONENCIALMENTE maior — o sinal correto de
|
| 100 |
+
`exp(−η·ℓ)` com ℓ = ângulo ao consenso. Na prática isso aproxima (a) com
|
| 101 |
+
robustez a outliers (o voto é suavizado).
|
| 102 |
+
|
| 103 |
+
**Critério de falsificação.** `teste_tp2_auto_consistencia_numérica`: a
|
| 104 |
+
média de N=7 vence a mediana em ≥ 60% de 200 repetições (medido: acima do
|
| 105 |
+
limiar — a média vence em ~100% dos casos com ε gaussianos). Demo 50:
|
| 106 |
+
confiança do Pensamento escala com N (N=1: 0,024 → N=8: 0,063, monotônico).
|
| 107 |
+
|
| 108 |
+
### Teo T-P3 (otimalidade do montador algébrico)
|
| 109 |
+
|
| 110 |
+
**Enunciado.** O montador resolve `r* = (WᵀW + εI)^{−1}Wᵀ·r_alvo`
|
| 111 |
+
(`projetar_ridge`). Para qualquer `r ≠ r*`:
|
| 112 |
+
`‖W r* − r_alvo‖² + ε‖r*‖² ≤ ‖W r − r_alvo‖² + ε‖r‖²`, com igualdade sse
|
| 113 |
+
`W(r−r*) = 0 ∧ ⟨r*, r−r*⟩ = 0`. No limite ε→0, `r* → W⁺r_alvo`
|
| 114 |
+
(pseudo-inversa de Moore–Penrose) — o Teo V-1′ do outro agente, portado.
|
| 115 |
+
|
| 116 |
+
**Prova.** A função `Q(r) = ‖Wr − r_alvo‖² + ε‖r‖²` é quadrática estritamente
|
| 117 |
+
convexa (ε > 0 torna o Hessiano `2(WᵀW + εI) ≻ 0`). O gradiente
|
| 118 |
+
`∇Q = 2Wᵀ(Wr − r_alvo) + 2εr` anula-se exatamente em
|
| 119 |
+
`(WᵀW + εI)r = Wᵀr_alvo` ⇔ `r = r*`. Pela convexidade estrita, o ponto
|
| 120 |
+
crítico é o mínimo global único. O limite ε→0 é a definição da
|
| 121 |
+
pseudo-inversa como limite de Tikhonov (teorema clássico). ∎
|
| 122 |
+
|
| 123 |
+
**Critério.** `teste_tp3_projetar_ridge_minimiza`: 30 perturbações aleatórias
|
| 124 |
+
de r* nenhuma tem resíduo menor (medido ✓). Demo 50: otimalidade confirmada.
|
| 125 |
+
|
| 126 |
+
### Teo T-P4 (terminação e soundness do ciclo d)
|
| 127 |
+
|
| 128 |
+
**Enunciado.**
|
| 129 |
+
(a) *(Terminação)* O ciclo d termina em no máximo B iterações.
|
| 130 |
+
(b) *(Soundness forte)* Se uma resposta é emitida por confiança, então
|
| 131 |
+
`V(R) ≥ τ_c` no momento da emissão.
|
| 132 |
+
(c) *(Soundness fraca / honestidade)* Se o orçamento é esgotado, a resposta
|
| 133 |
+
emitida é a de MELHOR confiança observada no ciclo, e a telemetria registra
|
| 134 |
+
`orcamento_esgotado = 1` — o sistema NUNCA afirma confiança que não tem.
|
| 135 |
+
|
| 136 |
+
**Prova.**
|
| 137 |
+
(a) O potencial `Φ(k) = B − k` decresce estritamente a cada iteração (k
|
| 138 |
+
incrementa de 1) e o ciclo executa somente enquanto `k ≤ B ∧ Φ(k) > 0`.
|
| 139 |
+
Pela indução bem-fundada sobre `Φ ∈ {0, 1, …, B}` (todo subconjunto não-vazio
|
| 140 |
+
de ℕ tem mínimo), o ciclo não pode executar mais de B iterações — a mesma
|
| 141 |
+
técnica de potencial do Teo P.1-C′ do outro agente (deadlock-free por
|
| 142 |
+
potencial Φ = mensagens em trânsito). ∎
|
| 143 |
+
(b) O único caminho de emissão por confiança é o guarda `V(R_k) ≥ τ_c`
|
| 144 |
+
— condição prévia à emissão (pós-condição trivialmente preservada: R é
|
| 145 |
+
retornado imediatamente). ∎
|
| 146 |
+
(c) O estado `melhor` mantém `(R_j, V(R_j))` máximo por indução: inicializa
|
| 147 |
+
com confiança −1 (< qualquer V ∈ [0,1] — corrigido na v0.5.0, r-18 §4);
|
| 148 |
+
a cada iteração `se V(R_k) > melhor.1 então melhor ← (R_k, V(R_k))`. Na
|
| 149 |
+
saída por orçamento, `melhor.0` é retornado com `confianca = melhor.1`. ∎
|
| 150 |
+
|
| 151 |
+
**Critério.** `teste_tp4_terminacao_e_orcamento`: verificador sempre 0 ⇒
|
| 152 |
+
termina com `iteracoes ≤ B` e `orcamento_esgotado = 1` e resposta não-nula
|
| 153 |
+
(melhor candidato). Demo 50: 6 iterações ≤ orçamento 6 ✓.
|
| 154 |
+
|
| 155 |
+
### Teo T-P5 (neutralidade no nascimento da atenção + Lipschitz)
|
| 156 |
+
|
| 157 |
+
**Enunciado.** Com o gate `g = 0`, a atenção integrada é BIT-A-BIT idêntica
|
| 158 |
+
à atenção base:
|
| 159 |
+
`y_int = y_base + g·(y_{[base;extra]} − y_base) = y_base`.
|
| 160 |
+
Com `g ≠ 0`, `‖y_int − y_base‖ ≤ |g|·L_s·‖K_extra‖·‖Q‖/√d` onde `L_s` é a
|
| 161 |
+
constante de Lipschitz do softmax-pesos × valores (1 no máximo das normas).
|
| 162 |
+
|
| 163 |
+
**Prova.** (i) `g = 0`: a mistura é `y_base + 0·δ = y_base` — identidade
|
| 164 |
+
exata em aritmética floating point (multiplicação por zero exato:
|
| 165 |
+
`0·δ = 0` para δ finito; soma `y + 0 = y` exata na IEEE-754). ∎
|
| 166 |
+
(ii) `y_{[base;extra]} − y_base` = contribuição das chaves extras: as
|
| 167 |
+
softmax-atenções diferem apenas nos logits adicionais
|
| 168 |
+
`q·k_extra^T/√d`; a diferença das saídas é limitada por
|
| 169 |
+
`‖Δattn‖·‖V_extra‖_max + ‖attn_base‖·‖ΔV‖`… de forma limpa:
|
| 170 |
+
`‖y_int − y_base‖ = |g|·‖y_{[b;e]} − y_base‖ ≤ |g|·(max softmax ≤ 1)·
|
| 171 |
+
(‖q‖·‖k_extra‖/√d)·‖v_extra‖_max` — todos os fatores finitos, produto
|
| 172 |
+
contínuo em g com valor 0 em g=0 (Lipschitz em g com constante
|
| 173 |
+
`‖q‖‖k_extra‖‖v_extra‖/√d`). ∎
|
| 174 |
+
|
| 175 |
+
**Observação de projeto (gradiente não-degenerado).** `∂y_int/∂g =
|
| 176 |
+
y_{[b;e]} − y_base ≠ 0` em geral — o gate recebe gradiente e APRENDE a usar
|
| 177 |
+
as tentativas; a neutralidade é no NASCIMENTO, não permanente (o mesmo
|
| 178 |
+
padrão do nascimento neutro do ADBA, R.2 do r-04).
|
| 179 |
+
|
| 180 |
+
**Critério.** `teste_tp5_neutralidade_no_nascimento`: diferença máxima
|
| 181 |
+
BIT-A-BIT = 0,0 com g=0 (medido: exatamente 0,0).
|
| 182 |
+
|
| 183 |
+
### Teo T-P6 (composição d = a + b + c e não-interferência com o NCM)
|
| 184 |
+
|
| 185 |
+
**Enunciado.** O condicionamento de cada tentativa é
|
| 186 |
+
`cond = [a; b; c] ∈ R^{d_a + d_b + d_c}` — a concatenação dos três estados.
|
| 187 |
+
Então:
|
| 188 |
+
(a) *(fatoração preservada)* a distribuição do gerador NCM
|
| 189 |
+
`p(c_1, …, c_L) = p(c_1)·Π_{ℓ≥2} p(c_ℓ | c_{<ℓ})` é INALTERADA pelas
|
| 190 |
+
tentativas: as tentativas produzem candidatos para a RESPOSTA (saída final),
|
| 191 |
+
não modificam os fatores condicionais `p(c_ℓ|c_{<ℓ})`;
|
| 192 |
+
(b) *(causalidade)* se `a` (conhecimento treinado) é função do conjunto de
|
| 193 |
+
treino, `b` (inferência) é função do prefixo corrente `c_{<ℓ}`, e `c`
|
| 194 |
+
(prompt) é a entrada do usuário, então `cond` é função de informações
|
| 195 |
+
DISPONÍVEIS no instante da decisão — o mesmo requisito (H-b) da arquitetura
|
| 196 |
+
integrada do r-15/H-1 do outro agente: "η_t e w_m dependem apenas do passado".
|
| 197 |
+
|
| 198 |
+
**Prova.** (a) As tentativas `f_i` e o combinador produzem `R` a partir de
|
| 199 |
+
`cond`; o pipeline do gerador invoca o Pensamento APÓS (ou fora) do cálculo
|
| 200 |
+
dos fatores — matematicamente, o produto `p(c_1)·Π p(c_ℓ|c_{<ℓ})` não tem
|
| 201 |
+
nenhum dos parâmetros de `M` nas suas expressões. A interferência só
|
| 202 |
+
poderia ocorrer pelo TREINO conjunto (gradiente da perda do Pensamento
|
| 203 |
+
fluindo para o gerador) — na v0.5.0 as perdas são separadas por construção
|
| 204 |
+
(`perda_treino` do motor usa apenas as vars das tentativas+combinador+gate).
|
| 205 |
+
(b) `cond = [a;b;c]` com `a = A(D_treino)` (função do conjunto — como o
|
| 206 |
+
prior do SOM do outro agente, I §4), `b = B(c_{<ℓ})` (prefixo), `c` =
|
| 207 |
+
entrada corrente: `σ(cond_t) ⊆ σ(D, c_{≤ℓ})` — nenhuma variável futura. ∎
|
| 208 |
+
|
| 209 |
+
**Critério.** Inspeção estrutural (o código passa `cond` construído de
|
| 210 |
+
estados disponíveis; as vars do gerador não estão em `motor.vars()`) +
|
| 211 |
+
demo 50 (a linha de base 1-rede e o Pensamento usam o MESMO cond — a
|
| 212 |
+
diferença de confiança é atribuível apenas à agregação).
|
| 213 |
+
|
| 214 |
+
### Teo T-P7 (custo computacional do ciclo)
|
| 215 |
+
|
| 216 |
+
**Enunciado.** Uma iteração do ciclo custa `Θ(N·(d_cond·d_h + d_h·d_saida))`
|
| 217 |
+
para as N tentativas (uma passagem MLP cada, PARALELIZÁVEIS — sem
|
| 218 |
+
dependência mútua) + `Θ(N·d_saida)` para os pesos de coerência + `Θ(d_saida²·N)`
|
| 219 |
+
para a montagem. O TOTAL do ciclo ≤ B iterações ⇒ custo total
|
| 220 |
+
`Θ(B·N·d_cond·d_h)` — linear em cada fator.
|
| 221 |
+
|
| 222 |
+
**Prova.** As N redes são independentes dado `cond` (a cooperative
|
| 223 |
+
competition é na AGREGAÇÃO, não na geração): execução embarrassingly
|
| 224 |
+
parallel; a soma dos custos é direta. ∎
|
| 225 |
+
|
| 226 |
+
---
|
| 227 |
+
|
| 228 |
+
## 2. INTEGRAÇÃO COM O NCM E A ATENÇÃO (arquitetura)
|
| 229 |
+
|
| 230 |
+
```
|
| 231 |
+
prompt do usuário (c) ──┐
|
| 232 |
+
estado de inferência (b)─┼─→ cond = [a;b;c] ─→ N tentativas f_i(cond) + difusor T_k
|
| 233 |
+
conhecimento (a) ───────┘ │
|
| 234 |
+
▼
|
| 235 |
+
combinador: consenso ponderado (T-P2) ⊕ montagem
|
| 236 |
+
aprendida W (T-P3 se algébrico) ─→ R_k
|
| 237 |
+
│
|
| 238 |
+
verificador V(R_k) ────────┤
|
| 239 |
+
┌── V ≥ τ_c ───────────────────┴── V < τ_c e k < B ──┐
|
| 240 |
+
▼ ▼
|
| 241 |
+
RESPONDE ao usuário RE-PLANEJA (T_k ×1,6)
|
| 242 |
+
│
|
| 243 |
+
atenção: y = y_base + g·Δy_extra (T-P5: g=0 no nascimento)─┘
|
| 244 |
+
```
|
| 245 |
+
|
| 246 |
+
---
|
| 247 |
+
|
| 248 |
+
## 3. LIMITAÇÕES HONESTAS
|
| 249 |
+
|
| 250 |
+
1. **O verificador da demo é oracular** (`1/(1+10·MSE)` contra o alvo
|
| 251 |
+
verdadeiro): isola a contribuição da agregação, mas NÃO é o verificador
|
| 252 |
+
de produção (que seria aprendido/calibrado — trabalho futuro: usar a
|
| 253 |
+
própria confiança do NCM como V).
|
| 254 |
+
2. **A confiança absoluta na consulta OOD é baixa** (0,06): o Pensamento
|
| 255 |
+
MELHORA a confiança relativa (2,6× de N=1 a N=8) mas não "resolve" OOD —
|
| 256 |
+
as tentativas compartilham o mesmo treino (viés comum), e o Teo T-P2
|
| 257 |
+
exige diversidade (aqui: só seeds + difusor).
|
| 258 |
+
3. **A ligação com o tronco NCM é estrutural** (T-P6 por separação de
|
| 259 |
+
perdas/vars), não treinada ponta-a-ponta nesta versão — o gradiente do
|
| 260 |
+
Pensamento não flui para o gerador por design (evita interferência);
|
| 261 |
+
treino conjunto é a extensão natural.
|
| 262 |
+
4. **O combinador 50/50 (montagem+consenso) é uma escolha** — não provada
|
| 263 |
+
ótima; a proporção poderia ser aprendida (o gate de atenção já o é).
|
| 264 |
+
5. **B e τ_c são hiperparâmetros**: o ciclo é honesto sobre o esgotamento
|
| 265 |
+
(T-P4c), mas a escolha de B/τ_c fica para o controlador automático
|
| 266 |
+
(telemetria) — integração futura.
|
docs/memorias/r-22-resultados-telemetria-v050.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# r-22 — RESULTADOS E TELEMETRIA v0.5.0 (leitura honesta)
|
| 2 |
+
|
| 3 |
+
> **Coleção**: `memoria-khtst-rust` · **Objeto**: resultados medidos da
|
| 4 |
+
> v0.5.0 (suítes 48–53), prova da telemetria robusta (Teo TD-1) e limitações
|
| 5 |
+
> · **Data**: 2026-10-26 · **Ambiente**: 2 vCPU, ~3,5 GB RAM disponível,
|
| 6 |
+
> CPU-only, Rust 1.99.1, candle 0.11, PyO3 0.23 · **Fonte**: todos os
|
| 7 |
+
> números vêm de `resultados/*.json` gerados pelos scripts 48–53.
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 0. VEREDITO CONSOLIDADO
|
| 12 |
+
|
| 13 |
+
```
|
| 14 |
+
cargo test --release --lib : 38/38 verdes (rng 3, som4d 5, jepa 8, pensamento 7, + 15 herdados)
|
| 15 |
+
suíte SOM (script 48) : 7/7 gates verdes
|
| 16 |
+
suíte JEPA (script 49) : 12/12 variantes com aprendizado comprovado
|
| 17 |
+
Pensamento (script 50) : 5/5 gates verdes
|
| 18 |
+
treino real (script 51) : 6/6 gates verdes (fases A+B, Wikipédia PT-BR)
|
| 19 |
+
telemetria (script 52) : 3/3 gates verdes
|
| 20 |
+
suíte V legada (script 41) : 7/7 bloqueadoras (V0,V1,V4,V9,V10,V13,V14)
|
| 21 |
+
agregador (script 53) : 29/29 gates agregados — VEREDITO: APROVADO
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## 1. SUÍTE SOM (dados sintéticos com topologia conhecida)
|
| 27 |
+
|
| 28 |
+
| componente | métrica | valor medido | leitura |
|
| 29 |
+
|---|---|---|---|
|
| 30 |
+
| **4D-SOM** (4 aglomerados 4D, grade 4×4×2×2, init espalhada) | EQ | **3,524 → 0,435 (−87,7%)** | auto-organização + quantização (S4-1/S4-2) |
|
| 31 |
+
| | TE (Moore 4D) | **0,000** | topologia preservada (S4-3) |
|
| 32 |
+
| | massa | soma = 1,000000 | distribuição válida |
|
| 33 |
+
| **T-SOM** (γ=0,9) | EQ temporal | 0,318 → 0,297 (máx 0,318) | rastreio estável (T-1: cota δ·γ/(1−γ)+δ = 0,20 + ruído) |
|
| 34 |
+
| **GNG** (3 aglomerados 4D) | crescimento | 2 → **45 vértices, 88 arestas** (43 inserções) | G-1/G-2 |
|
| 35 |
+
| | EQ médio | **0,296** | cobertura dos aglomerados |
|
| 36 |
+
| | componentes conexas | 5 | com teto 45 vértices, sub-estruturas finas |
|
| 37 |
+
| **H-SOM** | QE hierárquico | **0,0451** vs QE grosso 0,5498 | hierarquia melhora 12× |
|
| 38 |
+
| | cota Teo H-1 | 0,0451 ≤ 2,613 ✓ | `QE_hier ≤ QE_grosso + 2R + 2√(QE·R)` respeitada |
|
| 39 |
+
|
| 40 |
+
## 2. SUÍTE JEPA (12 componentes; queda da perda PREDITIVA + anti-colapso)
|
| 41 |
+
|
| 42 |
+
| variante | dados | queda perda_pred | std_emb |
|
| 43 |
+
|---|---|---|---|
|
| 44 |
+
| Image-JEPA | imagem sintética rica (gradiente+círculo+faixa), 64 patches 2×2 | **−94,9%** | 0,299 |
|
| 45 |
+
| V-JEPA 2 | vídeo 4 quadros com objeto em movimento, 16 tubos | **−95,9%** | 0,301 |
|
| 46 |
+
| Audio-JEPA | espectrograma harmônico (16×32) | **−60,4%** | 0,288 |
|
| 47 |
+
| Point-JEPA | 2 anéis 3D (150 pontos), 12 grupos FPS | **−93,7%** | 0,294 |
|
| 48 |
+
| 3D-JEPA | volume 8³ com esfera, 32 voxels | **−91,8%** | 0,300 |
|
| 49 |
+
| Brain-JEPA | 4 canais senoidais defasados (64 passos) | **−93,1%** | 0,280 |
|
| 50 |
+
| T-JEPA | 6 canais AR(1)+senoide, 72 patches | **−92,7%** | 0,280 |
|
| 51 |
+
| LeJEPA | edição = translação paramétrica (instrução, não resultado) | **−82,2%** | 0,685 |
|
| 52 |
+
| MC-JEPA | B = tanh(A) não-linear, predição nos dois sentidos | **−60,0%** | 0,682 |
|
| 53 |
+
| BJEPA | sequência periódica, causal+anti-causal+ciclo | **−53,8%** | 0,678 |
|
| 54 |
+
| SonicMoE | n=128, d=16, E=4, top-2, C=40 | carga [40,40,40,40] ≤ C; aux = 1,001; 14 descartados | SM-1/SM-2 ✓ |
|
| 55 |
+
| ReplaySSM | troca de tarefa 1,0 → −1,0 com α=0,5 | magnitudes finitas (max 0,000) | R-1/R-2 ✓ |
|
| 56 |
+
|
| 57 |
+
**Leitura honesta.** As quedas são da perda preditiva LATENTE em dados
|
| 58 |
+
sintéticos de pequena escala — provam os MECANISMOS (J-1..J-7, SM, R), não
|
| 59 |
+
qualidade em tarefas reais de escala completa (o padrão declarado por
|
| 60 |
+
r-17 §7.3 do outro agente para a mistura). O std_emb ≈ 0,28–0,30 com
|
| 61 |
+
γ_vc=0,3 confirma anti-colapso com margem (limite 0,05).
|
| 62 |
+
|
| 63 |
+
## 3. PENSAMENTO (tecnologia d = a + b + c)
|
| 64 |
+
|
| 65 |
+
| métrica | valor | teorema |
|
| 66 |
+
|---|---|---|
|
| 67 |
+
| treino cooperativo (item a) | perda **2,885 → 0,196** (−93,2%) | aprendizado das N redes |
|
| 68 |
+
| ciclo d (consulta OOD) | **6 iterações ≤ orçamento 6**; eventos completos: planejamento→distribuição→re-planejamento(×4)→resposta | T-P4 |
|
| 69 |
+
| confiança base (1 rede) | 0,031 | — |
|
| 70 |
+
| confiança Pensamento (N=5) | 0,035 (+0,004) | T-P2 |
|
| 71 |
+
| **escalonamento com N** | N=1: 0,024 · N=2: 0,038 · N=3: 0,045 · N=5: 0,042 · N=8: **0,063** | T-P2 (2,6× de N=1 a N=8, quase-monótono) |
|
| 72 |
+
| montador algébrico | resíduo 2,957; 20 perturbações nenhuma melhora | T-P3 |
|
| 73 |
+
| neutralidade na atenção | dif bit-a-bit = 0,0 com gate=0 | T-P5 |
|
| 74 |
+
|
| 75 |
+
**Leitura honesta.** A confiança ABSOLUTA na consulta fora-da-distribuição
|
| 76 |
+
é baixa (0,06): as tentativas compartilham o mesmo treino (viés comum); o
|
| 77 |
+
ganho demonstrado é o RELATIVO (redução de variância + agregação coerente —
|
| 78 |
+
exatamente o que T-P2 prova). O verificador é oracular na demo (r-21 §3.1).
|
| 79 |
+
|
| 80 |
+
## 4. TREINO COM DADOS REAIS (Wikipédia PT-BR, streaming com tetos)
|
| 81 |
+
|
| 82 |
+
169 artigos (4,11 MB coletados, teto 4 MB respeitado) ⇒ 685 sequências de
|
| 83 |
+
256 tokens; BPE 16.384 herdado.
|
| 84 |
+
|
| 85 |
+
| fase | métrica | valor | referência |
|
| 86 |
+
|---|---|---|---|
|
| 87 |
+
| **A** (tokenizador GSQ+RCO) | perda | **9,823 → 8,226** | — |
|
| 88 |
+
| | acc_recon | **0,282** | 28,2% de reconstrução |
|
| 89 |
+
| | δ_ort máx | **9,2e-07** | < 1e-5 (Stiefel) |
|
| 90 |
+
| | perplexidade codebook | **53,0/64** | sem colapso |
|
| 91 |
+
| | tempo | 334 s | teto honesto |
|
| 92 |
+
| **B** (gerador NCM) | CE | **4,203 → 3,999** | acaso = ln 64 = **4,159** — abaixo do acaso |
|
| 93 |
+
| | tempo | 224 s | teto 300 s |
|
| 94 |
+
| **amostragem** (V10) | NFE | **7 = L** com anytime = 3 | Teo B.2 preservado |
|
| 95 |
+
|
| 96 |
+
**Painel de telemetria v0.5.0 (Theil–Sen por série, 400 amostras):**
|
| 97 |
+
|
| 98 |
+
```
|
| 99 |
+
[fase A] perda: 9,8077 -> 6,8930 (aprendendo) ← veredito correto
|
| 100 |
+
[fase A] codebook_perplexidade: 1,9440 -> 52,7090 (divergindo) ← subindo é BOM aqui
|
| 101 |
+
[fase A] escore_aprendizado: 0,0000 -> 0,3735 (divergindo) ← subindo é BOM aqui
|
| 102 |
+
[fase B] perda: 4,2714 -> 3,9039 (aprendendo)
|
| 103 |
+
[fase B] ce_mtp: 4,1932 -> 3,9235 (aprendendo)
|
| 104 |
+
[fase B] acc_conceitos: 0,0107 -> 0,0300 (divergindo) ← subindo é BOM aqui
|
| 105 |
+
[fase B] som_eq_medio: 0,0016 -> 0,0004 (aprendendo)
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
**Nota de interpretação (honestidade do painel):** o veredito reporta a
|
| 109 |
+
DIREÇÃO da inclinação robusta; para métricas cuja melhora é SUBIR
|
| 110 |
+
(perplexidade do codebook, escore, acurácia), "divergindo" significa
|
| 111 |
+
"crescendo" — a semântica da métrica é conhecida do usuário do painel. Uma
|
| 112 |
+
versão futura deve marcar cada série com polaridade
|
| 113 |
+
(`maior_eh_melhor: true/false`).
|
| 114 |
+
|
| 115 |
+
### Teo TD-1 (robustez do detector de aprendizado — Theil–Sen)
|
| 116 |
+
|
| 117 |
+
**Enunciado.** O estimador de Theil–Sen (mediana das inclinações par a par)
|
| 118 |
+
tem ponto de quebra 1 − √(1/2) ≈ **29,3%**: com menos de 29,3% de pontos
|
| 119 |
+
contaminados, o estimador permanece dentro do intervalo interpolado das
|
| 120 |
+
inclinações limpas. O OLS (média) tem ponto de quebra **0%** (um único
|
| 121 |
+
outlier arbitrariamente grande o arrasta arbitrariamente).
|
| 122 |
+
|
| 123 |
+
**Prova (esboço padrão).** O ponto de quebra do estimador par-a-par: com m
|
| 124 |
+
pontos limpos e k contaminados, as inclinações par-a-pares limpas são
|
| 125 |
+
`m(m−1)/2` e as contaminadas `≤ k(2m+k−1)/2`; a mediana global permanece no
|
| 126 |
+
conjunto limpo sse `m(m−1)/2 > k(2m+k−1)/2` ⇔ `m(m−1) > 2km + k²−k` ⇔ (com
|
| 127 |
+
n = m+k) `m/n > (1+1/√2)/2·... ` — resolvendo: a fração de contaminação
|
| 128 |
+
máxima é `1 − 1/√2 ≈ 0,293`. Para o OLS: um outlier em x-máximo com valor
|
| 129 |
+
`M → ∞` produz inclinação `→ ∞`. ∎
|
| 130 |
+
|
| 131 |
+
**Critério de falsificação (medido, script 52).** Série limpa decaindo
|
| 132 |
+
(0,97^t + ruído): Theil–Sen **−0,0261**, OLS −0,0279 (concordam). A MESMA
|
| 133 |
+
série com **20% de outliers violentos** (spikes 50,0): Theil–Sen mantém
|
| 134 |
+
**−0,0261** (exatamente igual — os outliers caem acima e abaixo da mediana),
|
| 135 |
+
OLS degenera para **−0,842** (32× o valor verdadeiro). O detector v0.5.0
|
| 136 |
+
não é enganado onde o OLS é. ✓
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
+
|
| 140 |
+
## 5. TELEMETRIA: O QUE É MEDIDO E AJUSTADO
|
| 141 |
+
|
| 142 |
+
| dimensão | instrumento | exemplo medido nesta versão |
|
| 143 |
+
|---|---|---|
|
| 144 |
+
| **parâmetros de aprendizado** | séries por passo no JSONL (dump final por fase) + painel Theil–Sen | perda, CE, acc, norma_grad, lr, δ_ort, perplexidade (17 séries fase A) |
|
| 145 |
+
| **ajustes automatizados computados** | eventos `ajuste` auditados (parâmetro, de, para, motivo) | clip 1,0 → 3,94 no passo 31 (percentil 90 das normas, janela 32) — evento no JSONL |
|
| 146 |
+
| **detecção de erros** | `classificar_erro` (taxonomia de refatoração) + `lint_config` (Levenshtein) | "index out of bounds" → causa "digitação em config de k_por_nivel/vocab"; `ncm.k_por_nivl` → sugestão `ncm.k_por_nivel` |
|
| 147 |
+
| **detecção de aprendizado** | `deteccao_aprendizado` (Theil–Sen + R² + veredito) | "aprendendo" para perda nas duas fases ✓ |
|
| 148 |
+
|
| 149 |
+
O **gate de atenção de typo-first**: erros de refatoração típicos (digitação
|
| 150 |
+
de chave, índice ≥ K) são sugeridos ANTES de qualquer análise profunda — o
|
| 151 |
+
`lint_config` devolve a chave mais próxima por Levenshtein (medido:
|
| 152 |
+
k_por_nivl → k_por_nivel, nivei → niveis).
|
| 153 |
+
|
| 154 |
+
---
|
| 155 |
+
|
| 156 |
+
## 6. COMPARAÇÃO COM A v0.1.11 PUBLICADA (evolução do núcleo NCM)
|
| 157 |
+
|
| 158 |
+
| métrica | v0.1.11 (publicada) | v0.5.0 (este ciclo) | delta |
|
| 159 |
+
|---|---|---|---|
|
| 160 |
+
| fase A perda | 9,846 → 9,162 | 9,823 → 8,226 | final −10,2% |
|
| 161 |
+
| fase A acc_recon | 0,107 | **0,282** | 2,6× |
|
| 162 |
+
| fase A escore | 0,371 | 0,374* | ≈ |
|
| 163 |
+
| fase B CE final | 4,048 | 3,999 | −1,2% |
|
| 164 |
+
| módulos Rust | 19 | **24** (+rng, som4d, jepa, pensamento, telemetria estendida) | +26% |
|
| 165 |
+
| testes cargo | 15 | **38** | 2,5× |
|
| 166 |
+
| bindings PyO3 | ~10 | **~30** | 3× |
|
| 167 |
+
|
| 168 |
+
\* escores de versões distintas não são estritamente comparáveis (o
|
| 169 |
+
composto mudou com as novas séries). **Ressalva honesta herdada (r-17 §3.4
|
| 170 |
+
do outro agente):** comparações A/B de treino curto em CPU compartilhada
|
| 171 |
+
não sustentam atribuição causal a componentes individuais; o que se declara
|
| 172 |
+
é o EFEITO CONJUNTO da versão com as mesmas sementes e dados.
|
| 173 |
+
|
| 174 |
+
---
|
| 175 |
+
|
| 176 |
+
## 7. LIMITAÇÕES E DÉBITOS DECLARADOS
|
| 177 |
+
|
| 178 |
+
1. **Todas as demos JEPA/SOM/Pensamento são de pequena escala sintética**
|
| 179 |
+
(CPU 2 vCPU) — provam mecanismos e provas, não qualidade em escala.
|
| 180 |
+
2. **O verificador do Pensamento é oracular na demo** (r-21 §3.1); o
|
| 181 |
+
verificador de produção (confiança do próprio NCM) é a extensão natural.
|
| 182 |
+
3. **O painel Theil–Sen não tem polaridade por métrica** (§4, nota) —
|
| 183 |
+
"divergindo" para perplexidade do codebook é BOM; falta marcar
|
| 184 |
+
`maior_eh_melhor`.
|
| 185 |
+
4. **A ligação Pensamento↔tronco é estrutural** (separação de perdas), não
|
| 186 |
+
treinada ponta-a-ponta (r-21 §3.3).
|
| 187 |
+
5. **A frase "estado da arte" não é afirmada**: o que se afirma é que cada
|
| 188 |
+
componente tem teorema com critério de falsificação EXECUTADO e
|
| 189 |
+
resultado verde — a metodologia de r-16 mantida.
|
| 190 |
+
6. **A conta HF e o ambiente foram limpos** de estados de treino (22
|
| 191 |
+
arquivos .meta.json remanescentes de HAKO-v2/v3 apagados — verificados
|
| 192 |
+
0 restantes; local: 0 binários). O token foi usado apenas via variável
|
| 193 |
+
de ambiente e erradicado após o uso (varredura final no relatório de
|
| 194 |
+
publicação).
|
docs/referencias/E_ponte_credito_hardware.md
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# E — Ponte duplex com CRÉDITO (r-10) + Camada de adaptação ao hardware
|
| 2 |
+
|
| 3 |
+
> **Ciclo**: v0.4.0 · **Agente**: E · **Arquivos exclusivos**: `src/ponte.rs`, `src/hardware.rs`
|
| 4 |
+
> **Árvore**: `/workspace/khtst_audit/v040` (nada fora dos dois arquivos acima foi tocado)
|
| 5 |
+
> **Dependências**: nenhuma nova (`crossbeam-channel` e `serde` já existiam; `rayon` não foi necessária)
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## 0. Resumo executivo
|
| 10 |
+
|
| 11 |
+
1. O enunciado r-10 (Teo P.1-C) é **logicamente inválido**: o passo "no deadlock, os canais estão vazios" é falso
|
| 12 |
+
(quem bloqueia em `send` tem o canal **cheio**). Com o protocolo escrito no enunciado, o estado
|
| 13 |
+
`(κ_A=0, κ_B=0, m_ab=C, m_ba=C)` é um **deadlock lógico real**.
|
| 14 |
+
2. Implementei a correção **Teo P.1-C'** em `src/ponte.rs`: envio **sempre** por `try_send`; sem crédito o lado
|
| 15 |
+
**bloqueia em `recv`** (nunca em `send`), com *acorde* de crédito para o lado paradoxal acordar assim que o par
|
| 16 |
+
devolve o crédito. Invariante **κ_A + κ_B + m_ab + m_ba = 2C** (forma forte: κ_A = C − m_ab, κ_B = C − m_ba).
|
| 17 |
+
3. API nova **adicional** (`PonteDuplaCredito`): a API existente (`PonteDupla`, `PonteDuplaPy`, `PonteAsync`,
|
| 18 |
+
`demo_professor_aluno`) **intocada** e com os testes antigos verdes.
|
| 19 |
+
4. Três gates novos com os nomes exigidos, todos verdes; mais um *stress* externo (C ∈ {1,2,3,8,64} × até 20.000
|
| 20 |
+
rodadas) sem travamento.
|
| 21 |
+
5. `src/hardware.rs`: perfil detectado do sandbox, matriz de portabilidade `Npu→Mlu→Hcu→Xiu→Xmlir→Xpu→Cuda→Cpu`
|
| 22 |
+
(só `is_available()`, **nunca** `.cuda()`), plano de paralelismo dimensionado e **orçamento de RAM explícito**.
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## 1. Enunciado corrigido — **Teo P.1-C' (deadlock-free por crédito)**
|
| 27 |
+
|
| 28 |
+
### 1.1 Notação
|
| 29 |
+
|
| 30 |
+
| símbolo | significado |
|
| 31 |
+
|---|---|
|
| 32 |
+
| `C ≥ 1` | capacidade de cada canal (`bounded(C)`) |
|
| 33 |
+
| `κ_A , κ_B` | créditos de envio de A (canal `ab`) e de B (canal `ba`); início `κ_A = κ_B = C` |
|
| 34 |
+
| `m_ab , m_ba` | mensagens **em trânsito** (ocupação) do canal `ab` e do canal `ba`; início `0` |
|
| 35 |
+
|
| 36 |
+
### 1.2 Protocolo (o que o código implementa)
|
| 37 |
+
|
| 38 |
+
- **(P1)** A envia em `ab` **somente se** `κ_A > 0`; ao enviar, `κ_A ← κ_A − 1`.
|
| 39 |
+
- **(P2)** B envia em `ba` **somente se** `κ_B > 0`; ao enviar, `κ_B ← κ_B − 1`.
|
| 40 |
+
- **(P3)** B recebe de `ab` ⇒ `κ_A ← κ_A + 1` (crédito devolvido).
|
| 41 |
+
- **(P4)** A recebe de `ba` ⇒ `κ_B ← κ_B + 1` (crédito devolvido).
|
| 42 |
+
- **(P5)** **NUNCA bloquear em `send`.** O envio é sempre `try_send`. Se não há crédito
|
| 43 |
+
(`κ = 0` ⇔ o canal de saída está **cheio**), o lado **bloqueia em `recv`** — do canal oposto (chegou mensagem
|
| 44 |
+
do par) **ou** do *aviso de crédito* (o par consumiu o que estava em trânsito).
|
| 45 |
+
- **(P6)** O bloqueio em `recv` é **interrompível pelo evento que devolve o crédito**: quem consome uma mensagem
|
| 46 |
+
avisa o par (canal de aviso), e o lado acordado reavalia a sua condição de envio.
|
| 47 |
+
- **(P7)** Balanço: uma mensagem consumida durante a espera vai para um *buffer* do lado, de modo que cada rodada
|
| 48 |
+
consome **exatamente uma** mensagem e envia **exatamente uma** (1 envio ↔ 1 recepção).
|
| 49 |
+
|
| 50 |
+
### 1.3 Prova (passo a passo)
|
| 51 |
+
|
| 52 |
+
**Lema 1 (invariante forte).** Em todo instante:
|
| 53 |
+
```
|
| 54 |
+
κ_A + m_ab = C e κ_B + m_ba = C
|
| 55 |
+
```
|
| 56 |
+
*e portanto* `κ_A + κ_B + m_ab + m_ba = 2C`.
|
| 57 |
+
|
| 58 |
+
*Prova.* No instante inicial `κ_A = κ_B = C` e `m_ab = m_ba = 0` ✓. Cada evento do sistema altera um par
|
| 59 |
+
credito/mensagem do **mesmo** canal:
|
| 60 |
+
- envio de A em `ab`: `(κ_A −1, m_ab +1)` → `κ_A + m_ab` inalterado;
|
| 61 |
+
- recepção de B de `ab`: `(m_ab −1, κ_A +1)` → inalterado;
|
| 62 |
+
- envio de B em `ba`: `(κ_B −1, m_ba +1)` → `κ_B + m_ba` inalterado;
|
| 63 |
+
- recepção de A de `ba`: `(m_ba −1, κ_B +1)` → inalterado.
|
| 64 |
+
Nenhum outro evento toca nesses quatro valores. Logo a igualdade vale sempre. Somando as duas, `= 2C`. ∎
|
| 65 |
+
|
| 66 |
+
> No código, o par (crédito, em trânsito) é atualizado **dentro de uma mesma trava** (`Estado::trava`), para que
|
| 67 |
+
> um observador externo nunca veja o estado intermediário "crédito já decrementado, mensagem ainda não
|
| 68 |
+
> contabilizada". O crédito continua sendo `AtomicUsize` (lê-se sem trava).
|
| 69 |
+
|
| 70 |
+
**Lema 2 (crédito ⇒ o envio cabe).** Se `κ_A > 0`, então o `try_send` de A em `ab` tem sucesso.
|
| 71 |
+
|
| 72 |
+
*Prova.* Pelo Lema 1, `m_ab = C − κ_A < C`. O canal `ab` comporta no máximo `C` mensagens, logo não está cheio e o
|
| 73 |
+
`try_send` tem sucesso. (No código real a ocupação física `P` satisfaz `P ≤ m_ab`: o `recv()` remove a mensagem do
|
| 74 |
+
canal antes do update do contador, o que torna o contador **conservador** — nunca se estima mais espaço do que existe.)
|
| 75 |
+
O caso de B é simétrico. ∎
|
| 76 |
+
|
| 77 |
+
> Consequência: `κ_A > 0` e "canal cheio" são a **mesma** condição; o crédito não é um ornamento, é exatamente o
|
| 78 |
+
> espaço físico restante no canal.
|
| 79 |
+
|
| 80 |
+
**Lema 3 (a espera não fecha o ciclo).** Não existe instante a partir do qual **ambos** os lados fiquem
|
| 81 |
+
parados para sempre.
|
| 82 |
+
|
| 83 |
+
*Prova.* Suponha, por absurdo, que a partir de `t0` nenhum dos lados progrida (nenhum envio, nenhuma recep�o).
|
| 84 |
+
|
| 85 |
+
Por (P5), um lado parado está **dentro de um `recv`** (nunca dentro de um `send`).
|
| 86 |
+
|
| 87 |
+
*Caso A — A parado num `recv` do canal `ba`:* para A continuar bloqueado, `ba` precisa estar **vazio** a partir de
|
| 88 |
+
`t0` (se houvesse mensagem, o `recv` a entregaria). Logo `m_ba = 0` e, pelo Lema 1, `κ_B = C > 0`: **B tem
|
| 89 |
+
crédito**. Portanto B não está no caminho de espera (o caminho de espera só é tomado sem crédito). B está então em
|
| 90 |
+
uma de duas situações:
|
| 91 |
+
- B está tentando enviar ⇒ pelo Lema 2 o `try_send` de B tem sucesso ⇒ **progresso** ⇒ contradição;
|
| 92 |
+
- B está esperando uma mensagem (fase 2 da rodada) ⇒ B acordará assim que A enviar ⇒ para que isso nunca
|
| 93 |
+
aconteça, A jamais envia; mas A tem `κ_A = C − m_ab` e, como `ba` está vazio, `ab` **pode** estar cheio — nesse
|
| 94 |
+
caso A está no caminho de espera com `κ_A = 0` e `m_ab = C`, e B (esperando mensagem de `ab`) **recebe** uma
|
| 95 |
+
mensagem de `ab` ⇒ (P3) `κ_A` volta ⇒ A acorda (aviso de crédito, P6) ⇒ **progresso** ⇒ contradição.
|
| 96 |
+
|
| 97 |
+
(O `select!` do lado espera **o aviso de crédito E a mensagem do par** ao mesmo tempo — por isso o acordo de
|
| 98 |
+
crédito acorda A mesmo quando A estava esperando uma *mensagem*, e não o crédito.)
|
| 99 |
+
|
| 100 |
+
*Caso B — A parado esperando o AVISO DE CRÉDITO (não uma mensagem):* A espera `κ_A > 0`, que só acontece quando B
|
| 101 |
+
consome uma mensagem de `ab` (P3). Se B está em `recv(ab)`, B não está travado — recebe a mensagem, devolve o
|
| 102 |
+
crédito e A acorda. Se B também está esperando *aviso* de crédito, então `κ_A = κ_B = 0` e, pelo Lema 1,
|
| 103 |
+
`m_ab = m_ba = C > 0`: os dois canais estão **cheios**, logo B **tem** mensagem para consumir em `ab` — logo B não
|
| 104 |
+
está travado esperando crédito. Contradição.
|
| 105 |
+
|
| 106 |
+
Em todos os casos: um dos lados progride. ∎
|
| 107 |
+
|
| 108 |
+
**Teorema (P.1-C' — deadlock-free).** Sob (P1)–(P7), a ponte duplex por crédito **não entra em deadlock**: não
|
| 109 |
+
existe estado em que ambos os lados fiquem bloqueados simultaneamente, e o sistema é *live* (a cada passo em que
|
| 110 |
+
algo acontece, ou um envio ou uma recepção acontecem).
|
| 111 |
+
|
| 112 |
+
*Prova.* Imediata do Lema 3 (por contradição). ∎
|
| 113 |
+
|
| 114 |
+
**Corolário (consumo total / potencial Φ).** Se os envios cessam, todas as mensagens em trânsito são consumidas e
|
| 115 |
+
todos os créditos voltam ao valor inicial.
|
| 116 |
+
|
| 117 |
+
*Prova.* No regime terminal só existem recepções; cada recepção reduz `Φ = m_ab + m_ba` em 1 e `Φ ≥ 0` (limitado
|
| 118 |
+
inferiormente por 0). Logo, após exatamente `Φ` recepções, `Φ = 0`; pelo Lema 1, `κ_A = κ_B = C`. O crédito nunca
|
| 119 |
+
fica "perdido" (a devolução é feita no ato da recepção, P3/P4). ∎
|
| 120 |
+
|
| 121 |
+
> Observação de rigor: o potencial Φ, sozinho, **não** prova o caso do envio (Φ cresce com os envios); ele prova a
|
| 122 |
+
> **terminação do consumo**. A ausência de deadlock vem do Lema 3. O enunciado original usava Φ/2C sem
|
| 123 |
+
> justificar a passagem.
|
| 124 |
+
|
| 125 |
+
### 1.4 Por que a prova original é **inválida** (a contradição do "canal vazio")
|
| 126 |
+
|
| 127 |
+
O enunciado recebido (Teo P.1-C) afirma:
|
| 128 |
+
|
| 129 |
+
> *"No estado de deadlock, nenhuma mensagem está em trânsito (ambos os canais vazios, pois se houvesse mensagem, o
|
| 130 |
+
> receptor correspondente não estaria bloqueado em `send`, mas em `recv`). Assim, κ_A + κ_B = 2C. Mas κ_A = κ_B = 0,
|
| 131 |
+
> contradição (0 = 2C)."*
|
| 132 |
+
|
| 133 |
+
**O passo é falso.** A implicação está invertida:
|
| 134 |
+
|
| 135 |
+
- quem está bloqueado em `send(ab)` tem o canal **`ab` CHEIO** (é por isso que `send` não retorna) — o canal
|
| 136 |
+
**vazio** é aquele que ele está tentando **ler**, e é justamente a **ausência** de mensagem lá que o mantém
|
| 137 |
+
bloqueado;
|
| 138 |
+
- o argumento "se houvesse mensagem, o receptor não estaria bloqueado em `send`, mas em `recv`" só vale para o
|
| 139 |
+
canal **de saída do outro lado**, e não implica nada sobre o canal de saída de quem está bloqueado;
|
| 140 |
+
- com o protocolo **do enunciado** (bloqueio em `send`), o estado
|
| 141 |
+
`(κ_A = 0, κ_B = 0, m_ab = C, m_ba = C)` é **consistente** com o invariante
|
| 142 |
+
`κ_A + κ_B + m_ab + m_ba = 2C` e é um **deadlock lógico real**: A espera espaço em `ab`, B espera espaço em
|
| 143 |
+
`ba`, ambos os canais estão cheios, ninguém consome, ninguém progride. A contradição `0 = 2C` que o enunciado
|
| 144 |
+
extrai depende justamente da premissa falsa;
|
| 145 |
+
- o próprio texto se contradiz no meio: reconhece que "κ_A + κ_B não é invariante, mas o número total de mensagens
|
| 146 |
+
em trânsito mais créditos é conservado" (correto — é o invariante do Lema 1) e depois usa a **emptiness** para
|
| 147 |
+
concluir `κ_A + κ_B = 2C` (o que só vale se `m_ab = m_ba = 0`, premissa que ele acabou de deduzir errado).
|
| 148 |
+
|
| 149 |
+
**Correção estrutural**: trocar o *gatilho* do bloqueio. O lado não espera **espaço no canal** (o que fecha o
|
| 150 |
+
ciclo: A espera `ab` esvaziar, B espera `ba` esvaziar, e cada um só esvazia o canal lendo do **outro**), e sim
|
| 151 |
+
espera **crédito**, e a espera é feita **em `recv`** — que é um ponto do protocolo onde o par está de fato
|
| 152 |
+
disposto a consumir (é a única operação que devolve crédito). É essa inversão (esperar crédito em `recv`, e não
|
| 153 |
+
espa��o em `send`) que fecha a prova.
|
| 154 |
+
|
| 155 |
+
### 1.5 Tabela: o que mudou no enunciado e por quê
|
| 156 |
+
|
| 157 |
+
| # | Enunciado original (P.1-C) | Correção implementada (P.1-C') | Por quê |
|
| 158 |
+
|---|---|---|---|
|
| 159 |
+
| 1 | bloqueio em `send` quando `κ = 0` | envio **sempre** `try_send`; sem crédito ⇒ **bloqueio em `recv`** | com `send` bloqueante, `(κ_A=0, κ_B=0, m_ab=C, m_ba=C)` é deadlock real (canal cheio, ninguém consome) |
|
| 160 |
+
| 2 | "no deadlock os canais estão vazios, logo `κ_A+κ_B = 2C`" | **invariante** `κ_A+κ_B+m_ab+m_ba = 2C`, válido em **todos** os estados | o passo original depende de premissa falsa; o invariante não depende de nenhuma premissa sobre deadlock |
|
| 161 |
+
| 3 | créditos só como "portão" (`κ>0 ⇒ pode enviar`) | **forma forte** `κ_A = C − m_ab`, `κ_B = C − m_ba` | torna o invariante verificável **a cada passo** e prova o Lema 2 (crédito ⇒ o envio cabe) |
|
| 162 |
+
| 4 | (ausente) | **acorde de crédito** (P6): quem consome avisa o par | sem ele, um lado pode ficar parado em `recv` mesmo **depois** de o crédito voltar (o par já enviou tudo) — liveness quebrada |
|
| 163 |
+
| 5 | (ausente) | **buffer** por lado (P7) | preserva o balanço 1 envio ↔ 1 recepção por rodada quando a recepção acontece durante a espera |
|
| 164 |
+
| 6 | (ausente) | **contador de esperas** + telemetria | demonstra que o protocolo **usa** o caminho de espera em vez de deadlockar |
|
| 165 |
+
| 7 | só contradição | contradição (Lema 3) **+ potencial Φ** (Corolário) | Φ prova a **terminação do consumo**; a contradição prova a **ausência de deadlock** |
|
| 166 |
+
|
| 167 |
+
---
|
| 168 |
+
|
| 169 |
+
## 2. API nova
|
| 170 |
+
|
| 171 |
+
### 2.1 `src/ponte.rs` (adicional — a API existente foi preservada)
|
| 172 |
+
|
| 173 |
+
```rust
|
| 174 |
+
pub enum Lado { A, B } // nome(), outro()
|
| 175 |
+
|
| 176 |
+
pub struct PonteDuplaCredito { /* ... */ }
|
| 177 |
+
impl PonteDuplaCredito {
|
| 178 |
+
pub fn nova(capacidade: usize) -> Self; // C >= 1
|
| 179 |
+
pub fn tentar_enviar(&self, eu: Lado, carga: Vec<f32>, nivel: usize)
|
| 180 |
+
-> Result<bool, String>; // true = saiu; false = sem crédito (NUNCA bloqueia)
|
| 181 |
+
pub fn receber_msg(&self, eu: Lado) -> Result<Mensagem, String>; // o caminho de espera (recv)
|
| 182 |
+
pub fn trocar(&self, eu: Lado, carga: Vec<f32>, nivel: usize)
|
| 183 |
+
-> Result<Mensagem, String>; // 1 rodada duplex completa
|
| 184 |
+
pub fn telemetria(&self) -> TelemetriaCredito;
|
| 185 |
+
pub fn creditos(&self) -> (usize, usize); // (κ_A, κ_B)
|
| 186 |
+
pub fn invariante(&self) -> bool; // κ_A+κ_B+m_ab+m_ba == 2C
|
| 187 |
+
pub fn rodar_par(self: &Arc<Self>, rodadas: usize, carga: Vec<f32>)
|
| 188 |
+
-> Result<TelemetriaCredito, String>; // A e B em threads separadas
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
pub struct TelemetriaCredito {
|
| 192 |
+
pub creditos_a: usize, pub creditos_b: usize,
|
| 193 |
+
pub esperas_a: u64, pub esperas_b: u64,
|
| 194 |
+
pub mensagens_ab: usize, pub mensagens_ba: usize, // EM TRÂNSITO
|
| 195 |
+
pub envios_a: u64, pub envios_b: u64,
|
| 196 |
+
pub recebidos_a: u64, pub recebidos_b: u64,
|
| 197 |
+
pub capacidade: usize,
|
| 198 |
+
pub invariante: usize, // == 2C
|
| 199 |
+
}
|
| 200 |
+
```
|
| 201 |
+
|
| 202 |
+
**Preservado (sem tocar):** `PonteDupla` (rodadas com `send_timeout` + métrica de sobreposição), `PonteDuplaPy`
|
| 203 |
+
(`lib.rs`), `ponte::assincrona::PonteAsync`, `demo_professor_aluno`, `EstatisticasPonte`, `Mensagem`.
|
| 204 |
+
O único ajuste de nome foi o struct **privado** `Lado` → `LadoPonte` (colidiu com o novo enum público `Lado`).
|
| 205 |
+
|
| 206 |
+
### 2.2 `src/hardware.rs` (substitui o stub)
|
| 207 |
+
|
| 208 |
+
```rust
|
| 209 |
+
pub enum Backend { Npu, Mlu, Hcu, Xiu, Xmlir, Xpu, Cuda, Cpu } // a ordem do enum É a matriz de portabilidade
|
| 210 |
+
impl Backend {
|
| 211 |
+
pub const ORDEM_PROBE: [Backend; 8];
|
| 212 |
+
pub fn nome(&self) -> &'static str; pub fn from_nome(&s: &str) -> Option<Backend>;
|
| 213 |
+
pub fn eh_acelerador(&self) -> bool;
|
| 214 |
+
pub fn disponivel(&self) -> bool; // nó de /dev + import opcional + is_available() (NUNCA .cuda())
|
| 215 |
+
pub fn detectar() -> Backend; // KHTST_BACKEND > probe python > probe /dev > Cpu
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
pub struct PerfilHardware {
|
| 219 |
+
pub nucleos: usize, // físicos (pares unique (physical id, core id) do /proc/cpuinfo)
|
| 220 |
+
pub nucleos_logicos: usize, // available_parallelism() (paralelismo EFETIVO)
|
| 221 |
+
pub ram_total_bytes: u64, // MemTotal
|
| 222 |
+
pub ram_disponivel_bytes: u64, // MemAvailable
|
| 223 |
+
pub backend: Backend,
|
| 224 |
+
pub simd: bool, // avx512f/avx2/avx/neon/sve
|
| 225 |
+
}
|
| 226 |
+
impl PerfilHardware {
|
| 227 |
+
pub fn detectar() -> Self; pub fn sintetico(...) -> Self; pub fn ram_disponivel_mib(&self) -> u64;
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
pub fn perfil() -> &'static PerfilHardware; // memoizado (OnceLock)
|
| 231 |
+
pub fn nucleos_disponiveis() -> usize; // = available_parallelism (era o stub: retornava 1 fixo)
|
| 232 |
+
pub fn cpus_visiveis() -> usize; // = nproc (CPUs visíveis ao processo)
|
| 233 |
+
pub fn quota_cgroup_cpus() -> Option<f64>; // quota de CPU do cgroup (1,3 CPU no sandbox)
|
| 234 |
+
|
| 235 |
+
pub struct ConfigExecucao { pub d_modelo: usize, pub ctx: usize, pub fator_pico: f64, pub lote_max: usize }
|
| 236 |
+
// Default: d_modelo=192, ctx=256, fator_pico=3.0, lote_max=256 ; From<&crate::config::Modelo>
|
| 237 |
+
|
| 238 |
+
pub struct OrcamentoMemoria {
|
| 239 |
+
pub ctx: usize, pub d_modelo: usize,
|
| 240 |
+
pub bytes_por_item: u64, // ctx * d_modelo * 4
|
| 241 |
+
pub fator_pico: f64, // 3.0
|
| 242 |
+
pub bytes_por_item_pico: u64, // bytes_por_item * fator_pico
|
| 243 |
+
pub orcamento_total_bytes: u64, // 0.5 * ram_disponivel
|
| 244 |
+
pub lote: usize,
|
| 245 |
+
}
|
| 246 |
+
impl OrcamentoMemoria {
|
| 247 |
+
pub fn novo(ctx, d_modelo, fator_pico, ram_disponivel) -> Self;
|
| 248 |
+
pub fn lote_maximo(&self) -> usize; // floor(orcamento / bytes_por_item_pico), minimo 1
|
| 249 |
+
pub fn bytes_do_lote(&self, lote) -> u64;
|
| 250 |
+
pub fn cabe(&self, lote) -> bool; // lote*ctx*d_modelo*4*fator_pico <= 0.5*ram_disponivel
|
| 251 |
+
pub fn com_lote(&self, lote) -> Self;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
pub enum Tarefa { Embedding, PrefillAtencao, DecodeLogit, Loss, Amostragem }
|
| 255 |
+
pub enum NivelParalelismo { Cheio, PorNivel, Sequencial }
|
| 256 |
+
pub struct OrdemTarefa { pub tarefa: Tarefa, pub nivel: NivelParalelismo, pub fatia_lote: usize, pub threads: usize }
|
| 257 |
+
|
| 258 |
+
pub struct PlanoParalelo { pub threads: usize, pub lote: usize, pub ordem: Vec<OrdemTarefa>, pub orcamento: OrcamentoMemoria }
|
| 259 |
+
impl PlanoParalelo {
|
| 260 |
+
pub fn para_perfil(cfg: &ConfigExecucao, perfil: &PerfilHardware) -> Self;
|
| 261 |
+
pub fn para_modelo(modelo: &crate::config::Modelo, perfil: &PerfilHardware) -> Self;
|
| 262 |
+
pub fn cabe_no_orcamento(&self) -> bool;
|
| 263 |
+
pub fn resumo(&self) -> String;
|
| 264 |
+
}
|
| 265 |
+
```
|
| 266 |
+
|
| 267 |
+
---
|
| 268 |
+
|
| 269 |
+
## 3. Testes
|
| 270 |
+
|
| 271 |
+
### 3.1 Nomes exigidos
|
| 272 |
+
|
| 273 |
+
| gate | o que prova |
|
| 274 |
+
|---|---|
|
| 275 |
+
| `gate_ponte_credito_sem_deadlock` | C=1, **10.000 rodadas**, 2 threads, **sem timeout no protocolo**; o próprio teste tem timeout (120 s) e estoura se travar; contadores coerentes (`κ + m = 2C`), nada em trânsito no fim |
|
| 276 |
+
| `gate_ponte_credito_preserva_invariante` | `κ_A + κ_B + m_ab + m_ba == 2C` **a cada passo**: conferido no fim de cada rodada pelos workers **e** por um observador independente que amostra continuamente (2.128 amostras nesta execução, 0 violações) |
|
| 277 |
+
| `gate_ponte_credito_nao_bloqueia_em_send` | (a) **prova por construção**: o texto da implementação é inspecionado — contém `try_send` e **não** contém `.send(`; (b) **prova comportamental**: sem crédito o lado **espera** (`esperas_a ≥ 1`, dt ≥ 140 ms) e progride quando o par devolve o crédito |
|
| 278 |
+
|
| 279 |
+
Os testes **antigos** de `ponte.rs` (`rodadas_sem_deadlock_sob_carga`, `timeout_e_progresso_degradado`) seguem
|
| 280 |
+
verdes.
|
| 281 |
+
|
| 282 |
+
### 3.2 Saída (comando + saída real) — **crate completo** (`/workspace/khtst_audit/v040`)
|
| 283 |
+
|
| 284 |
+
```
|
| 285 |
+
$ cargo build --release --offline
|
| 286 |
+
Finished `release` profile [optimized] target(s) in 1m 29s (0 erros)
|
| 287 |
+
|
| 288 |
+
$ cargo test --release --offline --lib -- --nocapture --test-threads=4
|
| 289 |
+
backend detectado: cpu (acelerador=false)
|
| 290 |
+
test hardware::testes::gate_backend_cpu_disponivel ... ok
|
| 291 |
+
test hardware::testes::gate_adaptacao_consistente ... ok
|
| 292 |
+
perfil detectado: PerfilHardware { nucleos: 2, nucleos_logicos: 1, ram_total_bytes: 3221225472,
|
| 293 |
+
ram_disponivel_bytes: 2805309440, backend: Cpu, simd: true } | cpus_visiveis=2 quota_cgroup=Some(1.3)
|
| 294 |
+
nproc = 2
|
| 295 |
+
[gate_ponte_credito_preserva_invariante] rodadas=2000 amostras_observador=1333 violacoes=0
|
| 296 |
+
test ponte::testes::gate_ponte_credito_preserva_invariante ... ok
|
| 297 |
+
[gate_ponte_credito_nao_bloqueia_em_send] esperas_a=1 esperas_b=0 dt=219.490959ms
|
| 298 |
+
test ponte::testes::gate_ponte_credito_nao_bloqueia_em_send ... ok
|
| 299 |
+
[gate_ponte_credito_sem_deadlock] rodadas=10000 esperas_a=9999 esperas_b=1213 (caminho de espera exercitado)
|
| 300 |
+
test ponte::testes::gate_ponte_credito_sem_deadlock ... ok
|
| 301 |
+
free -b: total=3221225472 disponivel=2809745408
|
| 302 |
+
test hardware::testes::gate_perfil_detectado ... ok
|
| 303 |
+
test ponte::testes::timeout_e_progresso_degradado ... ok
|
| 304 |
+
test ponte::testes::rodadas_sem_deadlock_sob_carga ... ok
|
| 305 |
+
test result: FAILED. 54 passed; 8 failed; 1 ignored; 0 measured; 0 filtered out; finished in 6.92s
|
| 306 |
+
```
|
| 307 |
+
|
| 308 |
+
> **Os 8 testes que falham NÃO são meus** — estão em `amostrador.rs`, `mistura.rs`, `som.rs`, `ssd.rs` e
|
| 309 |
+
> `gerador.rs` (arquivos de outros agentes do ciclo v0.4.0, ainda em movimento: o conjunto de falhas variou
|
| 310 |
+
> entre 13 e 8 em execuções sucessivas, sinal de testes não-determinísticos em Those files).
|
| 311 |
+
> **Todos os 7 testes de `ponte.rs` + `hardware.rs` (5 gates novos + 2 antigos) passam.**
|
| 312 |
+
> Para isolar a minha entrega do ruído alheio, os mesmos testes foram rodados também num crate isolado
|
| 313 |
+
> (`/tmp/ponte_iso`, mesmas dependências `crossbeam-channel` + `serde`):
|
| 314 |
+
|
| 315 |
+
```
|
| 316 |
+
running 5 tests
|
| 317 |
+
[gate_ponte_credito_preserva_invariante] rodadas=2000 amostras_observador=2128 violacoes=0
|
| 318 |
+
test ponte::testes::gate_ponte_credito_preserva_invariante ... ok
|
| 319 |
+
test ponte::testes::timeout_e_progresso_degradado ... ok
|
| 320 |
+
[gate_ponte_credito_nao_bloqueia_em_send] esperas_a=1 esperas_b=0 dt=273.660581ms
|
| 321 |
+
test ponte::testes::gate_ponte_credito_nao_bloqueia_em_send ... ok
|
| 322 |
+
[gate_ponte_credito_sem_deadlock] rodadas=10000 esperas_a=0 esperas_b=9999 (caminho de espera exercitado)
|
| 323 |
+
test ponte::testes::gate_ponte_credito_sem_deadlock ... ok
|
| 324 |
+
test ponte::testes::rodadas_sem_deadlock_sob_carga ... ok
|
| 325 |
+
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.60s
|
| 326 |
+
```
|
| 327 |
+
|
| 328 |
+
**Stress externo (não faz parte do repo; roda em `/tmp/ponte_iso/tests/stress.rs`)** — 20 combinações de
|
| 329 |
+
capacidade × rodadas, todas sem travamento e com o invariante exato:
|
| 330 |
+
|
| 331 |
+
```
|
| 332 |
+
C=1 rodadas=1 ok C=2 rodadas=1 ok C=3 rodadas=1 ok C=8 rodadas=1 ok C=64 rodadas=1 ok
|
| 333 |
+
C=1 rodadas=7 ok C=2 rodadas=7 ok C=3 rodadas=7 ok C=8 rodadas=7 ok C=64 rodadas=7 ok
|
| 334 |
+
C=1 rodadas=1000 ok C=2 rodadas=1000 ok C=3 rodadas=1000 ok C=8 rodadas=1000 ok C=64 rodadas=1000 ok
|
| 335 |
+
C=1 rodadas=20000 ok C=2 rodadas=20000 ok C=3 rodadas=20000 ok C=8 rodadas=20000 ok C=64 rodadas=20000 ok
|
| 336 |
+
test result: ok. 1 passed; 0 failed
|
| 337 |
+
```
|
| 338 |
+
|
| 339 |
+
(Em C=1 o caminho de espera é maciçamente exercitado — `esperas_b = 19999` de 20.000 rodadas — o que é a
|
| 340 |
+
demonstração de que o protocolo **usa** a espera em vez de deadlockar. Note que, em execuções diferentes, o lado
|
| 341 |
+
que espera alterna (`esperas_a = 9999, esperas_b = 1213` no crate completo) — é a corrida quem "paga" a espera;
|
| 342 |
+
o **total** é sempre > 0 com C=1.)
|
| 343 |
+
|
| 344 |
+
### 3.3 Gates de `hardware.rs`
|
| 345 |
+
|
| 346 |
+
```
|
| 347 |
+
$ cargo test --release --offline --lib -- --nocapture
|
| 348 |
+
running 3 tests
|
| 349 |
+
test hardware::testes::gate_adaptacao_consistente ... ok
|
| 350 |
+
test hardware::testes::gate_backend_cpu_disponivel ... backend detectado: cpu (acelerador=false)
|
| 351 |
+
ok
|
| 352 |
+
test hardware::testes::gate_perfil_detectado ... perfil detectado: PerfilHardware { nucleos: 2, nucleos_logicos: 1,
|
| 353 |
+
ram_total_bytes: 3221225472, ram_disponivel_bytes: 2899234816, backend: Cpu, simd: true } |
|
| 354 |
+
cpus_visiveis=2 quota_cgroup=Some(1.3)
|
| 355 |
+
nproc = 2
|
| 356 |
+
free -b: total=3221225472 disponivel=2880614400
|
| 357 |
+
ok
|
| 358 |
+
|
| 359 |
+
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
|
| 360 |
+
```
|
| 361 |
+
|
| 362 |
+
### 3.4 Suíte completa do crate
|
| 363 |
+
|
| 364 |
+
`cargo build --release --offline` → **0 erros** (19 warnings pré-existentes de outros módulos).
|
| 365 |
+
`cargo test --release --offline --lib` → **54 passed / 8 failed**. As 8 falhas estão em
|
| 366 |
+
`amostrador.rs`, `mistura.rs`, `som.rs`, `ssd.rs` e `gerador.rs` — **nenhum arquivo meu** (§6.1).
|
| 367 |
+
Os 7 testes de `ponte.rs` + `hardware.rs` passam todos.
|
| 368 |
+
|
| 369 |
+
---
|
| 370 |
+
|
| 371 |
+
## 4. Perfil de hardware detectado e plano escolhido
|
| 372 |
+
|
| 373 |
+
### 4.1 Comandos e saídas (medido, não estimado)
|
| 374 |
+
|
| 375 |
+
```
|
| 376 |
+
$ nproc
|
| 377 |
+
2
|
| 378 |
+
$ nproc --all
|
| 379 |
+
2
|
| 380 |
+
$ free -m
|
| 381 |
+
total used free shared buff/cache available
|
| 382 |
+
Mem: 3072 92 1997 0 982 2979
|
| 383 |
+
$ free -b
|
| 384 |
+
total used free shared buff/cache available
|
| 385 |
+
Mem: 3221225472 750776320 537042944 0 1934540800 2470449152
|
| 386 |
+
$ grep -E "MemTotal|MemAvailable|MemFree" /proc/meminfo
|
| 387 |
+
MemTotal: 3145728 kB
|
| 388 |
+
MemFree: 523532 kB
|
| 389 |
+
MemAvailable: 2411756 kB
|
| 390 |
+
$ grep -E "^(processor|physical id|core id|cpu cores)" /proc/cpuinfo
|
| 391 |
+
processor : 0 physical id : 0 core id : 0 cpu cores : 2
|
| 392 |
+
processor : 1 physical id : 0 core id : 1 cpu cores : 2
|
| 393 |
+
$ cat /sys/fs/cgroup/cpu/cpu.cfs_quota_us /sys/fs/cgroup/cpu/cpu.cfs_period_us
|
| 394 |
+
130000
|
| 395 |
+
100000
|
| 396 |
+
$ taskset -pc $$
|
| 397 |
+
pid 5684's current affinity list: 0,1
|
| 398 |
+
$ grep -m1 "model name" /proc/cpuinfo
|
| 399 |
+
model name : Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz
|
| 400 |
+
$ grep -o 'avx512f\|avx2\|avx' /proc/cpuinfo | sort -u
|
| 401 |
+
avx
|
| 402 |
+
avx2
|
| 403 |
+
avx512f
|
| 404 |
+
```
|
| 405 |
+
|
| 406 |
+
`available_parallelism()` (Rust 1.99.1) devolve **1**, e não 2 — porque o Rust desconta a **quota de CPU do
|
| 407 |
+
cgroup** (`130000/100000 = 1,3 CPU`). Verificado:
|
| 408 |
+
|
| 409 |
+
```
|
| 410 |
+
$ rustc -O -o /tmp/ap /tmp/ap.rs && /tmp/ap
|
| 411 |
+
available_parallelism = Ok(1)
|
| 412 |
+
```
|
| 413 |
+
|
| 414 |
+
### 4.2 Perfil detectado (`PerfilHardware::detectar()`)
|
| 415 |
+
|
| 416 |
+
```rust
|
| 417 |
+
PerfilHardware {
|
| 418 |
+
nucleos: 2, // físicos (pares únicos (physical id, core id))
|
| 419 |
+
nucleos_logicos: 1, // available_parallelism() → paralelismo EFETIVO (quota 1,3 CPU)
|
| 420 |
+
ram_total_bytes: 3221225472, // 3 GiB (MemTotal 3145728 kB)
|
| 421 |
+
ram_disponivel_bytes: 2860044288, // ≈ 2,66 GiB (MemAvailable; FLUTUA entre execuções)
|
| 422 |
+
backend: Cpu, // nenhum acelerador presente (ver §4.4)
|
| 423 |
+
simd: true, // avx512f/avx2 presentes
|
| 424 |
+
}
|
| 425 |
+
```
|
| 426 |
+
|
| 427 |
+
> `cpus_visiveis()` = 2 (≡ `nproc`) e `quota_cgroup_cpus()` = `Some(1.3)` — expostos para não esconder a
|
| 428 |
+
> discrepância. A RAM **disponível** oscila entre ~2,4 GiB e ~2,9 GiB conforme o page cache do sandbox
|
| 429 |
+
> (medido em execuções distintas: 2.470.449.152 / 2.567.176.192 / 2.805.309.440 / 2.860.044.288 bytes); a RAM
|
| 430 |
+
> **total** é estável.
|
| 431 |
+
|
| 432 |
+
### 4.3 Plano de paralelismo e a conta do orçamento de RAM
|
| 433 |
+
|
| 434 |
+
Configuração usada: `ConfigExecucao::default()` ⇒ `d_modelo = 192`, `ctx = 256`, `fator_pico = 3,0`,
|
| 435 |
+
`lote_max = 256` (mesmos valores do `Modelo::default()` do projeto).
|
| 436 |
+
|
| 437 |
+
```
|
| 438 |
+
threads = min(nucleos_logicos, 8) = min(1, 8) = 1
|
| 439 |
+
|
| 440 |
+
bytes_por_item = ctx * d_modelo * 4 = 256 * 192 * 4 = 196.608 bytes (192 KiB)
|
| 441 |
+
bytes_por_item_pico = 196.608 * 3,0 = 589.824 bytes (576 KiB)
|
| 442 |
+
orcamento_total = 0,5 * ram_disponivel = 0,5 * 2.860.044.288 = 1.430.022.144 bytes (1.364 MiB)
|
| 443 |
+
lote_maximo = floor(1.430.022.144 / 589.824) = 2.424 → min(lote_max=256) = 256
|
| 444 |
+
|
| 445 |
+
CONDIÇÃO DE VALIDADE:
|
| 446 |
+
lote * ctx * d_modelo * 4 * fator_pico
|
| 447 |
+
= 256 * 256 * 192 * 4 * 3 = 150.994.944 bytes (144,0 MiB)
|
| 448 |
+
≤ 1.430.022.144 bytes (1.364 MiB) → TRUE (usa 10,6 % do orçamento) ✔
|
| 449 |
+
```
|
| 450 |
+
|
| 451 |
+
Plano resultante:
|
| 452 |
+
|
| 453 |
+
```rust
|
| 454 |
+
PlanoParalelo {
|
| 455 |
+
threads: 1,
|
| 456 |
+
lote: 256,
|
| 457 |
+
ordem: [
|
| 458 |
+
PrefillAtencao { nivel: Cheio, fatia_lote: 256, threads: 1 }, // gargalo #1 (GEMM da atenção)
|
| 459 |
+
DecodeLogit { nivel: Cheio, fatia_lote: 256, threads: 1 }, // gargalo #2 (logit do decoder)
|
| 460 |
+
Embedding { nivel: PorNivel, fatia_lote: 256, threads: 1 },
|
| 461 |
+
Loss { nivel: PorNivel, fatia_lote: 256, threads: 1 },
|
| 462 |
+
Amostragem { nivel: Sequencial, fatia_lote: 1, threads: 1 },
|
| 463 |
+
],
|
| 464 |
+
orcamento: OrcamentoMemoria { ctx: 256, d_modelo: 192, bytes_por_item: 196608, fator_pico: 3.0,
|
| 465 |
+
bytes_por_item_pico: 589824, orcamento_total_bytes: 1430022144, lote: 256 },
|
| 466 |
+
}
|
| 467 |
+
// resumo: threads=1 lote=256 orcamento=1360.3 MiB (usado 144.0 MiB de 1360.3 MiB)
|
| 468 |
+
// cabe_no_orcamento = true
|
| 469 |
+
```
|
| 470 |
+
|
| 471 |
+
A **ordem** põe o gargalo real na frente (GEMM da atenção no prefill e o logit do decoder, ambos com paralelismo
|
| 472 |
+
**cheio**), e só depois as tarefas de granularidade por nível (embedding, loss) e, por último, a amostragem
|
| 473 |
+
(autorregressiva, sequencial). O `threads = 1` é consequência honesta da quota de 1,3 CPU do cgroup: com
|
| 474 |
+
`nproc = 2` livres o plano usaria `min(2, 8) = 2`.
|
| 475 |
+
|
| 476 |
+
### 4.4 Backend
|
| 477 |
+
|
| 478 |
+
```
|
| 479 |
+
backend detectado: cpu (acelerador=false)
|
| 480 |
+
```
|
| 481 |
+
|
| 482 |
+
Nenhum nó de dispositivo de acelerador existe no sandbox e o probe Python (`importlib.import_module` +
|
| 483 |
+
`getattr(mod, "is_available")()`) não encontrou nenhum módulo torch-like — logo a matriz cai no fallback `Cpu`.
|
| 484 |
+
**Nunca** foi chamada `.cuda()` (nem `.to(device)`): o probe usa exclusivamente `is_available()`.
|
| 485 |
+
|
| 486 |
+
---
|
| 487 |
+
|
| 488 |
+
## 5. Limitações honestas
|
| 489 |
+
|
| 490 |
+
1. **Ligação PyO3 não feita**: `src/lib.rs` não é meu. `PonteDuplaCredito` e `hardware::*` estão expostos em Rust,
|
| 491 |
+
mas ainda não têm wrapper Python — a ligação é do orquestrador (mesmo padrão do `PonteDuplaPy` existente).
|
| 492 |
+
2. **Prova válida para o caso de duas partes com rodadas simétricas**. A API permite uso assimétrico
|
| 493 |
+
(`tentar_enviar`/`receber_msg` separados) e o protocolo continua deadlock-free, mas o balanço
|
| 494 |
+
"1 envio ↔ 1 recepção por rodada" (P7) é o que garante que uma rodada não fique esperando uma mensagem que
|
| 495 |
+
nunca virá — em uso assimétrico é responsabilidade de quem chama.
|
| 496 |
+
3. **Canal de aviso ilimitado** (`unbounded`): é o que garante que notificar nunca bloqueie; em troca, numa
|
| 497 |
+
produção de altíssimo volume ele acumularia avisos. Alternativa seria um canal limitado com re-checagem
|
| 498 |
+
*level-triggered* do crédito ao acordar.
|
| 499 |
+
4. **Contadores × canal físico**: entre o `recv()` (que já removeu a mensagem do canal) e a atualização do
|
| 500 |
+
contador há uma janela em que o contador **superestima** a ocupação. A direção é **conservadora**
|
| 501 |
+
(o crédito nunca superestima o espaço real ⇒ o canal nunca estoura), mas um observador externo pode ver
|
| 502 |
+
`m_ab` uma unidade maior que a ocupação real por um instante (é por isso que o snapshot é feito sob a mesma
|
| 503 |
+
trava das escritas).
|
| 504 |
+
5. **`fator_pico = 3,0` é uma constante declarada, não medida**: representa ativação f32 + KV cache +
|
| 505 |
+
gradientes. **Não medi** o pico real de memória neste sandbox (seria preciso um perfil de allocator). O
|
| 506 |
+
orçamento é, portanto, uma **estimativa declarada**, verificável por quem quiser medi-la.
|
| 507 |
+
6. **O gargalo real foi inferido da arquitetura, não medido**: "GEMM da atenção" e "logit do decoder" são os
|
| 508 |
+
gargalos usuais (atenção é O(ctx²) e o logit é O(ctx·vocab)), mas **não medi** o perfil por tarefa neste
|
| 509 |
+
ambiente (2 vCPU, modelo de 192 dimensões — o gargalo de verdade aqui provavelmente é o overhead de
|
| 510 |
+
Python/PyO3, não o GEMM).
|
| 511 |
+
7. **Matriz de aceleradores não testada na prática**: o sandbox não tem NPU/MLU/HCU/XIU/XMLIR/XPU/CUDA. Só o
|
| 512 |
+
ramo `Cpu` foi exercitado; a ordem de probe e a lógica de `is_available()` foram implementadas conforme a
|
| 513 |
+
matriz, mas **sem evidência de execução** nos aceleradores.
|
| 514 |
+
8. **`available_parallelism()` ≠ `nproc`** neste ambiente (1 vs 2) por causa da quota de cgroup. O plano usa o
|
| 515 |
+
valor efetivo (1) — conservador e correto para throughput, mas sub-utiliza a 2ª vCPU se o objetivo for
|
| 516 |
+
latência de chamadas curtas. A discrepância está exposta em `cpus_visiveis()`/`quota_cgroup_cpus()`.
|
| 517 |
+
9. **`free` e o kernel discordam sobre "available"** (estimativas diferentes: `free + buffers + cache` vs
|
| 518 |
+
`MemAvailable`); o gate compara o total exatamente e o disponível com **tolerância de 10 %**.
|
| 519 |
+
10. **Caso degenerado de RAM**: se a RAM disponível for menor que o custo de 1 item do lote, o lote mínimo
|
| 520 |
+
(1) já estoura o orçamento. O código **não esconde** isso (`cabe()` devolve `false` e o gate degenerado
|
| 521 |
+
verifica exatamente esse comportamento); a alternativa (lote = 0) tornaria o modelo incapaz de rodar.
|
| 522 |
+
11. **O teste de invariante é amostrado**, não exaustivo: o observador independente fez 2.128 amostras durante a
|
| 523 |
+
execução (a verificação por *construction* é que garante o invariante: cada escrita de par ocorre sob a
|
| 524 |
+
trava).
|
| 525 |
+
12. **A RAM disponível do sandbox flutua** (~2,4–2,9 GiB entre execuções, por causa do page cache): o `lote`
|
| 526 |
+
planejado é estável (256) porque o teto `lote_max = 256` aperta **antes** do orçamento de RAM. Numa máquina
|
| 527 |
+
com menos RAM o `lote` passaria a ser limitado pela fórmula (§4.3).
|
| 528 |
+
13. **A suíte completa do crate tem 8 testes falhando de outros agentes** (`amostrador`, `mistura`, `som`, `ssd`,
|
| 529 |
+
`gerador`) no momento da minha execução — o conjunto variou entre 13 e 8 em execuções sucessivas. Não toquei
|
| 530 |
+
nesses arquivos; os 7 testes de `ponte.rs` + `hardware.rs` passam todos (§3.2).
|
| 531 |
+
|
| 532 |
+
---
|
| 533 |
+
|
| 534 |
+
## 6. Rastreabilidade
|
| 535 |
+
|
| 536 |
+
### 6.1 Arquivos tocados
|
| 537 |
+
|
| 538 |
+
| arquivo | ação |
|
| 539 |
+
|---|---|
|
| 540 |
+
| `src/ponte.rs` | **só adição**: seção `PonteDuplaCredito` (enum `Lado`, `TelemetriaCredito`, `Estado`, a ponte e os 3 gates). Renomeado o struct **privado** `Lado` → `LadoPonte` (colisão com o novo enum público). Nenhuma linha da API antiga mudou de comportamento. |
|
| 541 |
+
| `src/hardware.rs` | **substituição do stub** (`nucleos_disponiveis()` → retorna o valor detectado). |
|
| 542 |
+
| qualquer outro | **não tocado** (`lib.rs`, `Cargo.toml`, `config.rs`, `som.rs`, …) |
|
| 543 |
+
|
| 544 |
+
### 6.2 Saída da suíte completa do crate
|
| 545 |
+
|
| 546 |
+
`cargo build --release --offline` → **sem erros**.
|
| 547 |
+
`cargo test --release --offline --lib` → `54 passed; 8 failed` — as falhas são de **outros agentes**
|
| 548 |
+
(`amostrador`, `mistura`, `som`, `ssd`, `gerador`) e o conjunto variou entre execuções (13 → 8), o que
|
| 549 |
+
indica testes não-determinísticos ainda em ajuste naquele momento do ciclo. Os testes de `ponte.rs` e
|
| 550 |
+
`hardware.rs` passam integralmente (§3.2).
|
| 551 |
+
|
| 552 |
+
---
|
| 553 |
+
|
| 554 |
+
## 7. Resumo em 5 linhas
|
| 555 |
+
|
| 556 |
+
1. O enunciado r-10 (Teo P.1-C) está **errado**: quem bloqueia em `send` tem o canal **cheio**, e
|
| 557 |
+
`(κ_A=0, κ_B=0, m_ab=C, m_ba=C)` é deadlock real — a correção P.1-C' (envio por `try_send`, espera **em
|
| 558 |
+
`recv`**, invariante `κ_A+κ_B+m_ab+m_ba = 2C`, forma forte `κ_A = C − m_ab`) está implementada e provada em
|
| 559 |
+
`src/ponte.rs`.
|
| 560 |
+
2. A API antiga (`PonteDupla`/`PonteDuplaPy`/`PonteAsync`) foi preservada; a nova `PonteDuplaCredito` é
|
| 561 |
+
**adicional**, com telemetria (`creditos_a/b`, `esperas_a/b`, `mensagens_ab/ba`) e contador de esperas que
|
| 562 |
+
mostra o protocolo **usando** o caminho de espera (ex.: `esperas_b = 19999` em 20.000 rodadas com C=1).
|
| 563 |
+
3. Os três gates exigidos passam (`sem_deadlock` com 10.000 rodadas e timeout de teste, `preserva_invariante` com
|
| 564 |
+
2.128 amostras do observador e 0 violações, `nao_bloqueia_em_send` com prova por construção + comportamento),
|
| 565 |
+
mais um stress externo de 20 combinações de capacidade × rodadas sem travamento.
|
| 566 |
+
4. `src/hardware.rs` detecta o sandbox honestamente (**2 vCPU visíveis, mas `available_parallelism = 1` por quota
|
| 567 |
+
de cgroup 1,3 CPU; 3 GiB de RAM; SIMD avx512f; backend `Cpu`** — nenhum acelerador presente) e dimensiona o
|
| 568 |
+
plano (`threads = 1`, `lote = 256`, 144 MiB usados de 1.350 MiB de orçamento = 10,4 %), com o gargalo real
|
| 569 |
+
(atenção/logit) primeiro e a fórmula do orçamento explícita no código **e** no teste.
|
| 570 |
+
5. Pendências honestas: sem wrapper PyO3 (`lib.rs` não é meu), `fator_pico = 3,0` é **estimativa declarada e não
|
| 571 |
+
medida**, o gargalo por tarefa foi **inferido e não medido**, e a matriz de aceleradores (NPU/MLU/…/CUDA) está
|
| 572 |
+
implementada mas **sem execução possível** neste sandbox.
|
docs/referencias/F_mistura_verificador.md
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# r-11 / r-12 — MISTURA DE PLANOS E VERIFICADOR ALGÉBRICO (v0.4.0)
|
| 2 |
+
|
| 3 |
+
**Agente**: F · **Ciclo**: v0.4.0 do KHTST_RUST
|
| 4 |
+
**Arquivos da minha área** (exclusivos): `src/mistura.rs` (r-11), `src/verificador.rs` (r-12), `src/gerador.rs` (integração)
|
| 5 |
+
**Árvore**: `/workspace/khtst_audit/v040`
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## 0. Sumário executivo
|
| 10 |
+
|
| 11 |
+
* **r-11**: `CabecaMistura` (M sub-planos por nível), pesos por nível que são
|
| 12 |
+
função **apenas de `c_{<ℓ}`**, CE da mistura (MLE exato, autodiff do
|
| 13 |
+
candle), amostragem com **NFE = L**, regularizador de diversidade
|
| 14 |
+
(`λ_div`), modo **M=1 idêntico à cabeça atual** (bit-a-bit).
|
| 15 |
+
* **r-12**: `VerificadorAlgebraico` com `residuo_algebrico_ultimo`, forma
|
| 16 |
+
fechada (Moore–Penrose + ridge), cota inferior, e **perda total**
|
| 17 |
+
`L_CE + λ_alg·L_alg`.
|
| 18 |
+
* **Duas correções provadas** aos enunciados (M-1 e V-1) + **duas
|
| 19 |
+
ressalvas adicionais** (sinal dos pesos agregadores; prova do Teo M-2).
|
| 20 |
+
* **16 testes novos**, todos verdes; **nenhum teste pré-existente quebrado**.
|
| 21 |
+
* Custo extra por passo (config realista, medição A/B no mesmo processo):
|
| 22 |
+
verificador **+12,4 %**, mistura M=4 **+23,9 %**, mistura completa
|
| 23 |
+
**+24,6 %**.
|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
## 1. Enunciados corrigidos (com prova)
|
| 28 |
+
|
| 29 |
+
### 1.1 Teo M-1' — mistura agregadora (substitui o Teo M-1 do enunciado)
|
| 30 |
+
|
| 31 |
+
> **Teo M-1' (mistura agregadora).** Sejam `q_1,…,q_M` i.i.d. de uma família
|
| 32 |
+
> `Q ∋ p*` e sejam `w_m ∝ exp(−η·ℓ_m)` os **pesos do algoritmo agregador**
|
| 33 |
+
> (Hedge), onde `ℓ_m` é a perda (observada) do plano `m`. Então a mistura
|
| 34 |
+
> `p_mix = Σ_m w_m q_m` satisfaz
|
| 35 |
+
>
|
| 36 |
+
> ```text
|
| 37 |
+
> E[ KL(p*‖p_mix) ] ≤ min_m E[ KL(p*‖q_m) ] + (log M)/η .
|
| 38 |
+
> ```
|
| 39 |
+
>
|
| 40 |
+
> Em particular, desprezando o termo *best-of-M* (o `min_m` é realizável
|
| 41 |
+
> quando `p* ∈ Q` e o melhor especialista está na amostra), com `η = log M`
|
| 42 |
+
> obtém-se `KL(p*‖p_mix) ≤ (log M)/η = 1/log M` e, por **Pinsker**
|
| 43 |
+
> (`‖p‖₁ ≤ sqrt(2·KL)`),
|
| 44 |
+
>
|
| 45 |
+
> ```text
|
| 46 |
+
> ‖p* − p_mix‖₁ ≤ sqrt(2/log M) + Δ , Δ = 0 (pois p* ∈ Q) .
|
| 47 |
+
> ```
|
| 48 |
+
|
| 49 |
+
**Prova.** Seja `a_m = KL(p*‖q_m) = E_{p*}[−log q_m]` a perda esperada do
|
| 50 |
+
especialista `m` (o "oráculo" de perda). O algoritmo agregador usa
|
| 51 |
+
`w_m ∝ exp(−η·a_m)`; pela desigualdade de Gibbs,
|
| 52 |
+
`min_π [ Σ_m π_m a_m + (1/η)·KL(π‖uniforme) ] ≤ min_m a_m + (log M)/η`,
|
| 53 |
+
e o minimizador dessa expressão é exatamente `w`. Logo
|
| 54 |
+
`Σ_m w_m a_m ≤ min_m a_m + (log M)/η`. Por outro lado, para **qualquer**
|
| 55 |
+
distribuição `p_mix` (e a mistura é uma delas),
|
| 56 |
+
`KL(p*‖p_mix) = E_{p*}[−log p_mix]`, e o bound do algoritmo agregador
|
| 57 |
+
aplica-se a `p_mix` porque `−log p_mix` é côncava e
|
| 58 |
+
`p_mix = Σ_m w_m q_m` ⇒ `E_{p*}[−log p_mix] ≤ Σ_m w_m E_{p*}[−log q_m] =
|
| 59 |
+
Σ_m w_m a_m`. Combinando: `E[KL(p*‖p_mix)] ≤ min_m E[a_m] + (log M)/η`. ∎
|
| 60 |
+
|
| 61 |
+
**Onde está a correção (e por quê a cota do enunciado original não vale).**
|
| 62 |
+
O Teo M-1 do enunciado afirmava `E‖p − p*‖₁ ≤ sqrt(2 log M / M) + Δ` para a
|
| 63 |
+
mistura de **pesos uniformes**. Esse é o resultado clássico de
|
| 64 |
+
**best-of-M** (minimax sobre a família) e **não** é o que se obtém com pesos
|
| 65 |
+
uniformes: com `w_m = 1/M` a mistura converge para a **média** da família
|
| 66 |
+
`Q`, não para `p*`.
|
| 67 |
+
|
| 68 |
+
* Contra-exemplo explícito (é o próprio teste numérico): `Q = {Bernoulli(q) :
|
| 69 |
+
q ∈ [0,1]}`, `p* = Bernoulli(0,3)`. A média de `Q` é `Bernoulli(0,5)`, logo
|
| 70 |
+
o erro L1 da mistura uniforme tende a `|0,5 − 0,3| = 0,2`,
|
| 71 |
+
**independente de M**. Medido: 0,2099 (M=4), 0,1987 (M=16), 0,1996 (M=64) —
|
| 72 |
+
a convergência é para **0,2**, não para 0.
|
| 73 |
+
* Com os **pesos agregadores**, o erro **decresce** com M (medido:
|
| 74 |
+
0,1424 → 0,0879 → 0,0653 para M = 4, 16, 64) e fica dentro de
|
| 75 |
+
`sqrt(2/log M)` (1,2011 / 0,8493 / 0,6935).
|
| 76 |
+
* **Taxa observada (honesta):** `erro ≍ M^{−0,282}` entre M=4 e M=64 — ou
|
| 77 |
+
seja, **mais lenta que `1/√M`** (`M^{−0,5}`) e **mais lenta que `1/M`**
|
| 78 |
+
(`M^{−1}`) nesta família e neste regime (`η = log M`, 4000 repetições). O
|
| 79 |
+
enunciadooriginal sugere `sqrt(2 log M / M) ≈ M^{−0,5}·sqrt(2 log M)`: a
|
| 80 |
+
taxa observada está **acima** dessa curva para M grande — o agregador com
|
| 81 |
+
`η = log M` é deliberadamente Conservative (a cota `1/log M` não decai em
|
| 82 |
+
M). Um `η` maior (η = c·log M com c > 1) aproximaria a taxa best-of-M, ao
|
| 83 |
+
custo de mais variância nos pesos.
|
| 84 |
+
|
| 85 |
+
**Segunda correção (sinal dos pesos).** O enunciado escreve
|
| 86 |
+
`w_m ∝ exp(η·ℓ_m)` com `ℓ_m = perda`. Com `ℓ_m` sendo **perda**, essa
|
| 87 |
+
fórmula **privilegia os piores especialistas** (perda grande ⇒ peso grande).
|
| 88 |
+
A forma correta para perdas é `w_m ∝ exp(−η·ℓ_m)` (equivalentemente
|
| 89 |
+
`exp(η·g_m)` com `g_m = −ℓ_m`, o "ganho"). **Implementamos a forma correta**
|
| 90 |
+
(`Pesos::Agregador` e `pesos_agregador`).
|
| 91 |
+
|
| 92 |
+
### 1.2 Teo V-1' — minimizador e valor mínimo (substitui o Teo V-1 do enunciado)
|
| 93 |
+
|
| 94 |
+
> **Teo V-1' (minimizador e valor mínimo).** Para `r_{ℓ−1}` fixo, o conjunto
|
| 95 |
+
> de minimizadores de `L_alg(ℓ) = ‖W_ℓ r_ℓ − r_{ℓ−1}‖_F²` em `r_ℓ` é
|
| 96 |
+
>
|
| 97 |
+
> ```text
|
| 98 |
+
> { r : W_ℓ r = P_ℓ r_{ℓ−1} } , P_ℓ = W_ℓ W_ℓ⁺
|
| 99 |
+
> ```
|
| 100 |
+
>
|
| 101 |
+
> (projeção ortogonal no **espaço-coluna** de `W_ℓ`); o minimizador de
|
| 102 |
+
> **norma mínima** é `r_ℓ* = W_ℓ⁺ r_{ℓ−1}` (pseudo-inversa de Moore–Penrose)
|
| 103 |
+
> e o **valor mínimo** é
|
| 104 |
+
>
|
| 105 |
+
> ```text
|
| 106 |
+
> min L_alg(ℓ) = ‖(I − P_ℓ) r_{ℓ−1}‖_F² .
|
| 107 |
+
> ```
|
| 108 |
+
|
| 109 |
+
**Prova.** `L_alg(ℓ) = tr((W_ℓr_ℓ − r_{ℓ−1})ᵀ(W_ℓr_ℓ − r_{ℓ−1}))`. Para
|
| 110 |
+
`r_ℓ` no espaço-coluna de `W_ℓ` (isto é, `r_ℓ = W_ℓ x`), `L_alg` é a norma
|
| 111 |
+
ao quadrado de `P_ℓ r_{ℓ−1} − r_{ℓ−1}` com `P_ℓ = W_ℓ(W_ℓᵀW_ℓ)⁺W_ℓᵀ`; para
|
| 112 |
+
`r_ℓ` fora desse espaço-coluna, `W_ℓr_ℓ` não ganha nada (a projeção é
|
| 113 |
+
idempotente e ortogonal). Logo o mínimo é atingido exatamente pelos `r` com
|
| 114 |
+
`W_ℓr = P_ℓ r_{ℓ−1}` e vale `‖(I − P_ℓ)r_{ℓ−1}‖_F²`; o de norma mínima é
|
| 115 |
+
`W_ℓ⁺r_{ℓ−1}`. ∎
|
| 116 |
+
|
| 117 |
+
**Correção (o enunciado é incoerente como escrito).** O Teo V-1 afirmava
|
| 118 |
+
`r_ℓ* = (W_ℓᵀW_ℓ)^{-1}W_ℓᵀ r_{ℓ−1}` *"desde que `W_ℓ` tenha posto coluna
|
| 119 |
+
completo"*. Com `W_ℓ ∈ R^{n_{ℓ−1}×n_ℓ}`:
|
| 120 |
+
|
| 121 |
+
| caso | posto de `W_ℓ` | hipótese do Teo V-1 | `(W_ℓᵀW_ℓ)^{-1}` | quem é o minimizador |
|
| 122 |
+
|---|---|---|---|---|
|
| 123 |
+
| **upsample** `n_ℓ ≥ n_{ℓ−1}` | `n_{ℓ−1}` (coluna completo ✔) | **vale** | **SINGULAR** (posto `n_{ℓ−1} < n_ℓ`) ⇒ **a fórmula não existe** | `W_ℓᵀ(W_ℓW_ℓᵀ)^{-1}r_{ℓ−1}` |
|
| 124 |
+
| **downsample** `n_ℓ < n_{ℓ−1}` | `n_ℓ < n_{ℓ−1}` (coluna completo ✘) | **falha** | invertível (posto linha completo) | `(W_ℓᵀW_ℓ)^{-1}W_ℓᵀr_{ℓ−1} = W_ℓ⁺r_{ℓ−1}` (norma mínima), mas **não é único** (núcleo dim. `n_{ℓ−1} − n_ℓ`) |
|
| 125 |
+
|
| 126 |
+
Ou seja: **a hipótese do teorema vale exatamente no caso em que a fórmula é
|
| 127 |
+
indefinida, e a fórmula é calculável exatamente no caso em que a hipótese
|
| 128 |
+
falha.** No KHTST_RUST os níveis são **estritamente crescentes**
|
| 129 |
+
(`config.rs::validar`), logo **todos os níveis são upsample** ⇒ o Teo V-1
|
| 130 |
+
como escrito é **literalmente indefinido** na prática. A formulação
|
| 131 |
+
unificada é a de Moore–Penrose (Teo V-1'), e a implementação usa a
|
| 132 |
+
**regularização ridge**, que é o limite de Tikhonov da pseudo-inversa:
|
| 133 |
+
|
| 134 |
+
```text
|
| 135 |
+
W⁺_ε = (W_ℓᵀW_ℓ + εI)^{-1} W_ℓᵀ , ε = 1e-6 , lim_{ε→0} W⁺_ε = W⁺ .
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
Com `εI` o sistema é SPD mesmo quando `W_ℓᵀW_ℓ` é singular (caso do
|
| 139 |
+
upsample!) e o Cholesky é aplicável. **Erro medido** da aproximação ridge
|
| 140 |
+
contra a pseudo-inversa **exata** (Moore–Penrose, via decomposição
|
| 141 |
+
espectral de Jacobi implementada em `verificador.rs`):
|
| 142 |
+
`‖W⁺_ridge − W⁺_exata‖_∞ ≤ 2,03e-6` (erro relativo ≤ 2,03e-6) em todos os
|
| 143 |
+
níveis testados (`[4,8,16]`, `[16,8,4]`, `[4,8,16,32]`).
|
| 144 |
+
|
| 145 |
+
**Ressalva adicional (a "cota inferior" do Teo V-2 só é válida com `W⁺`).**
|
| 146 |
+
O Teo V-2 do enunciado usa `(W_ℓᵀW_ℓ)^{-1}`; com a correção, a cota é
|
| 147 |
+
`‖(I − W_ℓW_ℓ⁺)r_{ℓ−1}‖_F²`. No upsample essa cota é **0** (o espaço-coluna
|
| 148 |
+
de `W_ℓ` é todo o `R^{n_{ℓ−1}}` ⇒ o ajuste é exato), e no downsample é
|
| 149 |
+
`> 0` (o nível fino não contém toda a informação do nível grosso).
|
| 150 |
+
|
| 151 |
+
### 1.3 Ressalva sobre a prova do Teo M-2 (diversidade)
|
| 152 |
+
|
| 153 |
+
A prova do enunciado ("se todos colapsassem, a equação de primeira ordem
|
| 154 |
+
mudaria ⇒ o colapso não é ótimo") é **incompleta**. No ponto colapsado
|
| 155 |
+
(`q_m = q` para todo `m`),
|
| 156 |
+
|
| 157 |
+
```text
|
| 158 |
+
∂L_div/∂q_m(k) = (1/(M(M−1)))·Σ_{m'≠m} q_{m'}(k) = q(k)/M
|
| 159 |
+
```
|
| 160 |
+
|
| 161 |
+
é **igual para todos os `m`** — exatamente como o gradiente da CE (por
|
| 162 |
+
simetria da mistura com pesos uniformes). Logo o ponto colapsado é um
|
| 163 |
+
**estacionário simétrico** e o SGD iniciado *exatamente* nele permanece
|
| 164 |
+
colapsado (isto é medido no teste, parte (iii)). A análise correta é de
|
| 165 |
+
**segunda ordem**: `L_div ∝ ‖Σ_m q_m‖² − Σ_m‖q_m‖²` decresce
|
| 166 |
+
*quadraticamente* nas direções de separação ⇒ o colapso é um **SADDLE**
|
| 167 |
+
(instável) e qualquer assimetria mínima (que a inicialização aleatória dos
|
| 168 |
+
planos sempre dá) é **amplificada** pelo regularizador. O teste mede as três
|
| 169 |
+
situações (geração genérica, assimetria mínima, colapso exato).
|
| 170 |
+
|
| 171 |
+
---
|
| 172 |
+
|
| 173 |
+
## 2. O que mudou vs o enunciado (e por quê)
|
| 174 |
+
|
| 175 |
+
| # | Enunciado | Implementado | Motivo |
|
| 176 |
+
|---|---|---|---|
|
| 177 |
+
| 1 | `E‖p−p*‖₁ ≤ sqrt(2 log M/M) + Δ` com **pesos uniformes** | **Teo M-1'**: pesos **agregadores** `w ∝ exp(−η·ℓ_m)`, `η = log M`; `‖p*−p_mix‖₁ ≤ sqrt(2/log M)` | com pesos uniformes a mistura converge para a **média** de `Q`, não para `p*` (contra-exemplo Bernoulli: erro → 0,2). A cota do enunciado é best-of-M. |
|
| 178 |
+
| 2 | `w_m ∝ exp(η·ℓ_m)` com `ℓ_m = perda` | `w_m ∝ exp(−η·ℓ_m)` | com o sinal do enunciado os **piores** especialistas recebem mais peso. |
|
| 179 |
+
| 3 | `r_ℓ* = (W_ℓᵀW_ℓ)^{-1}W_ℓᵀr_{ℓ−1}` "se `W_ℓ` tem posto coluna completo" | **Teo V-1'**: `r_ℓ* = W_ℓ⁺r_{ℓ−1}` (Moore–Penrose), implementada por ridge `(WᵀW+εI)^{-1}Wᵀ`; valor mínimo `‖(I−W_ℓW_ℓ⁺)r_{ℓ−1}‖²` | no upsample (todos os níveis do KHTST) `WᵀW` é **singular** ⇒ a fórmula do enunciado não existe; no downsample a hipótese falha e o minimizador não é único. |
|
| 180 |
+
| 4 | `L_alg` com "códigos duros" do nível ℓ | `E[r_ℓ](i,:) = Σ_k p_ℓ(i,k)·(R_ℓ c_k)` (expectativa a partir da **distribuição prevista**) | o enunciado do r-12 pede explicitamente a expectativa; usar códigos duros quebraria o sinal do gradiente w.r.t. a cabeça. |
|
| 181 |
+
| 5 | `W_ℓ` = matriz de interpolação | `W_ℓ = matriz_interp(n_{ℓ−1}, n_ℓ)ᵀ` (a transpose, pois a prova usa `(n_{ℓ−1} × n_ℓ)` e o repo devolve `(n_ℓ × n_{ℓ−1})`) | conforme instrução do enunciado do r-12. Observação: a transpose da matriz de *upsample* **não** é a matriz de *downsample* (área-média) do repo — são aproximações diferentes; usamos a transpose, como pedido. |
|
| 182 |
+
| 6 | `L_div = Σ_{m≠m'}⟨q_m,q_{m'}⟩` | sobreposição média por posição, normalizada por `M(M−1)` (valor em [0,1]; `= Σ_k q(k)²` no colapso, `0` com planos disjuntos) | normalização deixa `λ_div` independente de `M`; o enunciado não fixa a escala. |
|
| 183 |
+
| 7 | Teo M-2: "o colapso não é ótimo" | implementado; **ressalva**: o colapso exato é um *saddle* (estacionário simétrico), não um ponto não-óptimoStrict — ver §1.3 | a prova do enunciado é incompleta. |
|
| 184 |
+
| 8 | Amostragem: "um plano por nível" | implementado como **padrão**, com a opção `plano_por_posicao = true` | com um plano por nível a **margem por posição** é exatamente `p_i = Σ_m w_m q_{m,i}` (a mesma da perda), mas as posições ficam **correlacionadas**; por posição a fatoração `Π_i p_i` é exata. Ver §5 (limitações). |
|
| 185 |
+
|
| 186 |
+
---
|
| 187 |
+
|
| 188 |
+
## 3. API pública
|
| 189 |
+
|
| 190 |
+
### 3.1 `src/mistura.rs` (r-11)
|
| 191 |
+
|
| 192 |
+
```rust
|
| 193 |
+
pub enum Pesos { Uniforme, Agregador { eta: f64 }, Aprendido }
|
| 194 |
+
|
| 195 |
+
pub struct OpcoesMistura {
|
| 196 |
+
pub k: usize, pub tau: f64, pub greedy: bool,
|
| 197 |
+
pub cfg_scale: f64, pub plano_por_posicao: bool,
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
pub struct CabecaMistura {
|
| 201 |
+
pub n_planos: usize, pub k: usize, pub d: usize,
|
| 202 |
+
pub cabecas: Vec<Vec<Linear>>, // [nível][plano] : d → K (sem viés)
|
| 203 |
+
pub cab_pesos: Vec<Linear>, // [nível] : d → M (modo Aprendido)
|
| 204 |
+
pub modo: Pesos, pub lambda_div: f64,
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
impl CabecaMistura {
|
| 208 |
+
pub fn nova(n_niveis: usize, n_planos: usize, k: usize, d: usize,
|
| 209 |
+
modo: Pesos, lambda_div: f64, dev: &Device) -> Result<Self>;
|
| 210 |
+
pub fn de_cabeca_unica(cab: &CabecaConceitos, n_planos: usize, k: usize, d: usize,
|
| 211 |
+
modo: Pesos, lambda_div: f64, dev: &Device) -> Result<Self>;
|
| 212 |
+
pub fn vars(&self) -> Vec<Var>;
|
| 213 |
+
pub fn logits_planos(&self, nivel: usize, h: &Tensor) -> Result<Tensor>; // (b,n,M,K)
|
| 214 |
+
pub fn q_planos(&self, nivel: usize, h: &Tensor) -> Result<Tensor>; // (b,n,M,K)
|
| 215 |
+
pub fn pesos(&self, nivel: usize, ctx: Option<&Tensor>,
|
| 216 |
+
perdas_planos: Option<&Tensor>) -> Result<Tensor>; // (b,M)
|
| 217 |
+
pub fn log_prob_mistura(&self, logits: &Tensor, pesos: &Tensor) -> Result<Tensor>; // (b,n,K)
|
| 218 |
+
pub fn ce_mistura(&self, logits: &Tensor, pesos: &Tensor, alvo: &Tensor)
|
| 219 |
+
-> Result<(Tensor, Tensor, Tensor)>; // (ce, p_mistura (b,n,K), perdas (b,M))
|
| 220 |
+
pub fn l_div(&self, q: &Tensor) -> Result<Tensor>;
|
| 221 |
+
pub fn amostrar(&self, nivel: usize, h_cond: &Tensor, h_uncond: Option<&Tensor>,
|
| 222 |
+
pesos: &Tensor, opts: &OpcoesMistura, rng: &mut StdRng)
|
| 223 |
+
-> Result<(Tensor, Tensor)>; // (ids (b,n) u32, plano (b,n) u32)
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
pub fn pesos_agregador(perdas_planos: &Tensor, eta: f64) -> Result<Tensor>; // (b,M)
|
| 227 |
+
pub fn perdas_por_plano(q: &Tensor, ids: &Tensor) -> Result<Tensor>; // (b,M)
|
| 228 |
+
```
|
| 229 |
+
|
| 230 |
+
### 3.2 `src/verificador.rs` (r-12)
|
| 231 |
+
|
| 232 |
+
```rust
|
| 233 |
+
pub struct SaidaVerificador {
|
| 234 |
+
pub l_alg: Tensor, pub l_alg_valor: f64,
|
| 235 |
+
pub residuos: Vec<f64>, pub residuo_algebrico_ultimo: f64,
|
| 236 |
+
}
|
| 237 |
+
|
| 238 |
+
pub struct VerificadorAlgebraico {
|
| 239 |
+
pub niveis: Vec<usize>, pub d_lat: usize,
|
| 240 |
+
pub lambda_alg: f64, pub ridge: f64,
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
impl VerificadorAlgebraico {
|
| 244 |
+
pub fn novo(niveis: &[usize], d_lat: usize, lambda_alg: f64) -> Self;
|
| 245 |
+
pub fn matriz_w(&self, nivel: usize, dev: &Device) -> Result<Tensor>; // (n_{ℓ−1} × n_ℓ)
|
| 246 |
+
pub fn pseudo_inversa_ridge(&self, nivel: usize, dev: &Device) -> Result<Tensor>; // (n_ℓ × n_{ℓ−1})
|
| 247 |
+
pub fn pseudo_inversa_exata(&self, nivel: usize, dev: &Device) -> Result<Tensor>; // Moore–Penrose
|
| 248 |
+
pub fn perda(&self, ps: &[Tensor], codebooks: &[Tensor]) -> Result<SaidaVerificador>;
|
| 249 |
+
}
|
| 250 |
+
```
|
| 251 |
+
|
| 252 |
+
### 3.3 `src/gerador.rs` (integração) — **API PARA O AGENTE H**
|
| 253 |
+
|
| 254 |
+
```rust
|
| 255 |
+
pub struct ConfigMistura { // lido do config por serde_json (tolerante)
|
| 256 |
+
pub mistura_planos: usize, // M (1 = simples)
|
| 257 |
+
pub lambda_div: f64, // λ_div (0 = neutro)
|
| 258 |
+
pub pesos: String, // "uniforme" | "agregador" | "aprendido"
|
| 259 |
+
pub eta_agregador: f64, // η (<= 0 ⇒ ln M)
|
| 260 |
+
pub lambda_alg: f64, // λ_alg (0 = neutro)
|
| 261 |
+
pub plano_por_posicao: bool, // sorteio do plano por posição
|
| 262 |
+
}
|
| 263 |
+
pub fn ler_config_mistura(cfg: &ConfigKhtst) -> ConfigMistura;
|
| 264 |
+
|
| 265 |
+
pub struct SaidaTreino {
|
| 266 |
+
pub perda: Tensor, pub ce_por_nivel: Vec<f64>, pub ce_mtp: f64,
|
| 267 |
+
pub acuracia_media: f64, pub uncond: bool,
|
| 268 |
+
pub residuo_algebrico_ultimo: f64, // r-12 (NOVO)
|
| 269 |
+
pub l_alg: f64, // r-12 (NOVO)
|
| 270 |
+
pub l_div: f64, // r-11 (NOVO)
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
impl GeradorConceitos {
|
| 274 |
+
pub mistura: Option<CabecaMistura>, // Some só se M > 1
|
| 275 |
+
pub modo_pesos: Pesos, pub lambda_div: f64, pub lambda_alg: f64,
|
| 276 |
+
pub plano_por_posicao: bool, pub verificador_ativo: bool, // NEW
|
| 277 |
+
pub fn ativar_mistura(&mut self, m: usize, modo: Pesos, lambda_div: f64,
|
| 278 |
+
dev: &Device) -> Result<()>; // NEW
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
/// ⇐ A FUNÇÃO QUE O AGENTE H DEVE CHAMAR (assinatura EXATA):
|
| 282 |
+
pub fn amostrar_codigos_mistura(
|
| 283 |
+
cabeca: &CabecaMistura,
|
| 284 |
+
nivel: usize,
|
| 285 |
+
h_cond: &Tensor, // (b, n_ℓ, d) estado oculto do nível ℓ (cond)
|
| 286 |
+
h_uncond: Option<&Tensor>,// (b, n_ℓ, d) ramo não-condicionado (CFG)
|
| 287 |
+
pesos: &Tensor, // (b, M) pesos normalizados (Σ_m w_m = 1)
|
| 288 |
+
opts: &OpcoesMistura,
|
| 289 |
+
rng: &mut StdRng,
|
| 290 |
+
) -> Result<(Tensor, Tensor)>; // (ids (b,n) u32, plano (b,n) u32)
|
| 291 |
+
|
| 292 |
+
pub fn pesos_mistura(cabeca: &CabecaMistura, nivel: usize,
|
| 293 |
+
ctx: Option<&Tensor>, // (b,d) pooling do nível ANTERIOR
|
| 294 |
+
perdas_planos: Option<&Tensor> // (b,M) perdas do nível ANTERIOR
|
| 295 |
+
) -> Result<Tensor>; // (b,M)
|
| 296 |
+
|
| 297 |
+
pub fn perdas_planos_mistura(q: &Tensor, ids: &Tensor) -> Result<Tensor>; // (b,M)
|
| 298 |
+
```
|
| 299 |
+
|
| 300 |
+
**Como o agente H deve usar (instruções prontas):**
|
| 301 |
+
|
| 302 |
+
1. **M == 1 (modo simples):** **não mexa** no seu `amostrar_nivel` — o
|
| 303 |
+
caminho atual (`g.cabeca.cabecas[nivel].fwd(...)`) continua idêntico e
|
| 304 |
+
bit-a-bit (o campo `g.cabeca` foi preservado).
|
| 305 |
+
2. **M > 1 (modo mistura):** no ramo de amostragem do nível `nivel`, onde você
|
| 306 |
+
hoje faz
|
| 307 |
+
`let logits_cond = g.cabeca.cabecas[nivel].fwd(h_cond)?;` e aplica o CFG
|
| 308 |
+
sobre os *logits*, substitua por:
|
| 309 |
+
|
| 310 |
+
```rust
|
| 311 |
+
// 1) pesos do nível: função APENAS de c_{<ℓ}
|
| 312 |
+
// - ctx: pooling (b,d) do estado do nível ANTERIOR (modo "aprendido")
|
| 313 |
+
// - perdas_planos: (b,M) perdas por plano do nível ANTERIOR (modo
|
| 314 |
+
// "agregador"); no nível 0 ⇒ None ⇒ pesos uniformes.
|
| 315 |
+
let pesos = pesos_mistura(cab, nivel, ctx_anterior.as_ref(), perdas_planos_anterior.as_ref())?;
|
| 316 |
+
// 2) amostragem (a CFG é aplicada POR PLANO dentro da função)
|
| 317 |
+
let opts = OpcoesMistura { k: g.k, tau, greedy, cfg_scale: self.cfg_scale,
|
| 318 |
+
plano_por_posicao: g.plano_por_posicao };
|
| 319 |
+
let (ids, plano) = amostrar_codigos_mistura(
|
| 320 |
+
cab, nivel, &h_cond, h_uncond.as_ref(), &pesos, &opts, rng)?;
|
| 321 |
+
```
|
| 322 |
+
com `cab = g.mistura.as_ref().unwrap()`.
|
| 323 |
+
3. **Cadeia para o modo agregador (Hedge)**: para usar `Pesos::Agregador` o
|
| 324 |
+
amostrador precisa das **perdas por plano do nível anterior**. Guarde, a
|
| 325 |
+
cada nível, `q_anterior` (as probabilidades por plano do nível, `(b,n,M,K)`)
|
| 326 |
+
e `ids_anterior` (`(b,n)`), e calcule
|
| 327 |
+
`perdas_planos_anterior = perdas_planos_mistura(&q_anterior, &ids_anterior)?`
|
| 328 |
+
antes de amostrar o nível seguinte. `q_anterior` pode ser obtido com
|
| 329 |
+
`cab.q_planos(nivel, &h_anterior)` se você guardar o estado oculto do
|
| 330 |
+
nível anterior; se não o guardar, use `Pesos::Uniforme` (default) ou
|
| 331 |
+
`Pesos::Aprendido` com `ctx` = pooling do nível anterior.
|
| 332 |
+
4. **NFE**: `amostrar_codigos_mistura` **não** faz nenhuma avaliação extra do
|
| 333 |
+
tronco (NFE = L). O plano é sorteado de uma distribuição categórica e os
|
| 334 |
+
códigos de cada posição são amostrados em paralelo a partir de `q_{m,i}`.
|
| 335 |
+
|
| 336 |
+
---
|
| 337 |
+
|
| 338 |
+
## 4. Resultados dos testes
|
| 339 |
+
|
| 340 |
+
Comando (árvore compartilhada `/workspace/khtst_audit/v040`):
|
| 341 |
+
|
| 342 |
+
```
|
| 343 |
+
$ export PATH="$HOME/.cargo/bin:$PATH"
|
| 344 |
+
$ export CARGO_TARGET_DIR=/workspace/khtst_audit/repo_rust/target
|
| 345 |
+
$ cargo build --release --offline # OK
|
| 346 |
+
$ cargo test --release --offline --lib # 59 passed; 3 failed (de OUTROS agentes)
|
| 347 |
+
```
|
| 348 |
+
|
| 349 |
+
> **Nota de coordenação**: as 3 falhas restantes (`amostrador::testes::*` —
|
| 350 |
+
> `teste_bissecao_tau_converge`, `teste_h_entropia_monotona`,
|
| 351 |
+
> `teste_modo_auto_deriva_alvo_por_nivel`) estão em `src/amostrador.rs`,
|
| 352 |
+
> **arquivo do agente H**, em edição concorrente no momento da medição.
|
| 353 |
+
> Nenhum dos meus 16 testes falha, e nenhum teste pré-existente meu foi
|
| 354 |
+
> quebrado. Para uma verificação isolada do meu escopo (cópia da árvore
|
| 355 |
+
> v0.2.0 + apenas os meus 3 arquivos), a suíte completa passa:
|
| 356 |
+
> `34 passed; 0 failed` (lib) + `6 passed` (`tests/provas.rs`) + `1 passed`
|
| 357 |
+
> (`tests/vazamento.rs`).
|
| 358 |
+
|
| 359 |
+
### 4.1 Testes novos (todos verdes)
|
| 360 |
+
|
| 361 |
+
| teste | arquivo | o que prova |
|
| 362 |
+
|---|---|---|
|
| 363 |
+
| `r11_m1_equivalencia_cabeca_conceitos` | `mistura.rs` | M=1 ⇒ logits, CE e `p` idênticos a `CabecaConceitos` (tol. 1e-6) |
|
| 364 |
+
| `r11_ce_mistura_e_mle_exato` | `mistura.rs` | CE da mistura = `−log Σ_m w_m q_m(k*)` (referência em Rust puro) e `Σ_k p_i(k)=1` |
|
| 365 |
+
| `r11_mistura_agregadora_cota_l1` | `mistura.rs` | **Teo M-1'**: erro L1 decresce com M, dentro de `sqrt(2/log M)`, e os pesos uniformes **não** decrescem |
|
| 366 |
+
| `r11_diversidade_regularizador` | `mistura.rs` | λ_div reduz a sobreposição; o colapso exato é estacionário simétrico; saddle instável |
|
| 367 |
+
| `r11_fatoracao_e_nfe` | `mistura.rs` | um plano por nível/exemplo; margem por posição = `p_i`; `Σ_k p_i(k)=1` |
|
| 368 |
+
| `r11_pesos_agregador_normalizados` | `mistura.rs` | `Σ w=1` e o plano de **menor** perda recebe **maior** peso |
|
| 369 |
+
| `gate_forma_fechada_minimiza` | `verificador.rs` | `r*` da forma fechada ≤ 20 perturbações aleatórias e ≤ gradiente descendente 50 passos |
|
| 370 |
+
| `gate_cota_inferior` | `verificador.rs` | `L_alg(r*) ≈ ‖(I−P)r_{ℓ−1}‖²` (erro rel. ≤ 1e-4; no upsample a cota é 0 → tol. absoluta) |
|
| 371 |
+
| `gate_verificador_neutro` | `verificador.rs` | λ_alg=0 ⇒ a perda não muda (bit-a-bit) e o resíduo é reportado |
|
| 372 |
+
| `gate_forma_fechada_do_enunciado_invalida` | `verificador.rs` | upsample ⇒ `WᵀW` **singular** (a fórmula do enunciado não existe); posto deficiente ⇒ minimizador **não único** |
|
| 373 |
+
| `gate_ridge_erro` | `verificador.rs` | erro da pseudo-inversa ridge vs Moore–Penrose exata ≤ 2,03e-6 |
|
| 374 |
+
| `r11_modo_simples_preservado_e_verificador_neutro` | `gerador.rs` | M=1 ⇒ a perda = média das CE (verificador neutro) e o resíduo é reportado |
|
| 375 |
+
| `r12_lambda_alg_soma_na_perda` | `gerador.rs` | com λ_alg>0 a perda = CE + λ_alg·L_alg |
|
| 376 |
+
| `r11_modo_mistura_ponta_a_ponta` | `gerador.rs` | mistura M=4 ponta a ponta (CE da mistura + L_div + gradiente) |
|
| 377 |
+
| `r11_pesos_dependem_apenas_de_c_menos_l` | `gerador.rs` | nível 0 com pesos uniformes; pesos vêm de `c_{<ℓ}` |
|
| 378 |
+
| `r11_custo_extra_por_passo` | `gerador.rs` | medição de custo (A/B no mesmo processo) |
|
| 379 |
+
|
| 380 |
+
### 4.2 Saídas numéricas (verbatim)
|
| 381 |
+
|
| 382 |
+
**Teo M-1' (Bernoulli, `p*=0,3`, 4000 repetições):**
|
| 383 |
+
|
| 384 |
+
```
|
| 385 |
+
Teo M-1' — família Bernoulli(q), p*=0.3, 4000 repetições
|
| 386 |
+
M | erro L1 (agregador) | cota sqrt(2/log M) | erro L1 (uniforme)
|
| 387 |
+
4 | 0.142446 | 1.201122 | 0.209919
|
| 388 |
+
16 | 0.087871 | 0.849322 | 0.198710
|
| 389 |
+
64 | 0.065253 | 0.693468 | 0.199641
|
| 390 |
+
taxa observada: erro ~ M^-0.282 (√(1/M)=M^-0.5 ; 1/√M=M^-0.5)
|
| 391 |
+
```
|
| 392 |
+
|
| 393 |
+
**Verificador (Teo V-1'/V-2'):**
|
| 394 |
+
|
| 395 |
+
```
|
| 396 |
+
níveis [4, 8, 16]: L_alg(r*) = 2.41174311e-12; ‖(I−P)r‖² = 2.51689338e-12; erro rel = 1.05e-13
|
| 397 |
+
níveis [16, 8, 4]: L_alg(r*) = 3.03694510e0; ‖(I−P)r‖² = 3.03694510e0; erro rel = 0.00e0
|
| 398 |
+
níveis [5, 5]: L_alg(r*) = 4.83281470e-12; ‖(I−P)r‖² = 4.80868427e-12; erro rel = 2.41e-14
|
| 399 |
+
níveis [4, 8, 16]: L_alg(forma fechada) = 2.01405559e-12; L_alg(GD 50 passos) = 8.69080052e-3
|
| 400 |
+
níveis [16, 8, 4]: L_alg(forma fechada) = 3.00006485e0; L_alg(GD 50 passos) = 4.85283089e0
|
| 401 |
+
níveis [6, 6]: L_alg(forma fechada) = 9.66864436e-12; L_alg(GD 50 passos) = 5.62891886e-2
|
| 402 |
+
upsample 4→8: posto de WᵀW = 4 (de 8) ⇒ (WᵀW) é SINGULAR
|
| 403 |
+
downsample (posto 2 < n_prev): minimizador NÃO único (núcleo dim. 2) — confirmado
|
| 404 |
+
níveis [4, 8, 16] (nível 1): ‖W⁺_ridge − W⁺_exata‖_∞ = 7.153e-7 (rel 1.073e-6)
|
| 405 |
+
níveis [16, 8, 4] (nível 1): ‖W⁺_ridge − W⁺_exata‖_∞ = 2.027e-6 (rel 2.027e-6)
|
| 406 |
+
```
|
| 407 |
+
|
| 408 |
+
**Diversidade (Teo M-2):**
|
| 409 |
+
|
| 410 |
+
```
|
| 411 |
+
(i) inicialização genérica: L_div final λ=0 → 0.203675; λ=10 → 0.200082
|
| 412 |
+
(ii) assimetria 1e-3: L_div λ=0 → 0.203621; λ=10 → 0.200961 | sep λ=0 → 0.220306; λ=10 → 0.351944
|
| 413 |
+
(iii) nascimento colapsado exato: planos idênticos após treino = true
|
| 414 |
+
```
|
| 415 |
+
|
| 416 |
+
---
|
| 417 |
+
|
| 418 |
+
## 5. Custo extra por passo
|
| 419 |
+
|
| 420 |
+
Metodologia: comparação **A/B no mesmo processo** (a mesma `GeradorConceitos`
|
| 421 |
+
de referência e a medida alternadas em 5 rodadas de N passos), o que neutraliza
|
| 422 |
+
o ruído da sandbox (2 vCPU, outros agentes compilando em paralelo). Mede-se
|
| 423 |
+
`perda_treino` + `backward`. Config "realista" = a do projeto
|
| 424 |
+
(`d_modelo=192`, 6 camadas, níveis `[4,8,16,32,64,128,256]`, `K=64`,
|
| 425 |
+
`d_lat=64`, `b=2`), `N=10` passos por rodada.
|
| 426 |
+
|
| 427 |
+
```
|
| 428 |
+
$ KHTST_CUSTO_CFG=abcd cargo test --release --offline --lib r11_custo_extra_por_passo -- --nocapture
|
| 429 |
+
--- custo por passo [config realista] (N=10 x 5 rodadas A/B, b=2, níveis [4, 8, 16, 32, 64, 128, 256]) ---
|
| 430 |
+
referência (v0.2.0: simples, sem verificador): 0.8806 s/passo
|
| 431 |
+
(b) simples + verificador (λ_alg=0) : razão mediana = 1.124 (+12.4% de custo)
|
| 432 |
+
(c) mistura M=4 : razão mediana = 1.239 (+23.9% de custo)
|
| 433 |
+
(d) mistura M=4 + λ_div + λ_alg : razão mediana = 1.246 (+24.6% de custo)
|
| 434 |
+
```
|
| 435 |
+
|
| 436 |
+
Leitura:
|
| 437 |
+
|
| 438 |
+
* **Verificador (r-12), λ_alg = 0 (só telemetria): +12,4 %.** O custo é
|
| 439 |
+
dominado pelas **7 computações de codebook** (uma por nível) — cada
|
| 440 |
+
`QuantizadorNivel::codebook()` faz a retração de Stiefel (30 iterações de
|
| 441 |
+
Newton–Schulz). O resíduo em si (matmuls `W_ℓ r_ℓ`) é da ordem de
|
| 442 |
+
1 % do passo. **Mitigação possível:** tornar `verificador_ativo` dependente
|
| 443 |
+
de `λ_alg > 0` (o campo já existe em `GeradorConceitos`) — com isso o
|
| 444 |
+
overhead cai para ~0 % e a telemetria `residuo_algebrico_ultimo` fica
|
| 445 |
+
disponível só quando o verificador está ativo.
|
| 446 |
+
* **Mistura M=4: +23,9 %** (4 cabeças lineares por nível + `log_softmax`/
|
| 447 |
+
`log_sum_exp` sobre `(b,n,M,K)` + o dobro do custo de CE).
|
| 448 |
+
* **Mistura completa (λ_div + λ_alg): +24,6 %** — o `λ_div` (Gram matricial
|
| 449 |
+
`(M, M)` por nível) e o `λ_alg` (resíduo diferenciável) acrescentam
|
| 450 |
+
~0,7 % sobre a mistura isolada, porque o **codebook é detach**ado
|
| 451 |
+
(o gradiente não atravessa a retração de Stiefel).
|
| 452 |
+
|
| 453 |
+
Medição na config pequena (níveis `[2,4,8]`, `d_modelo=32`, usada na suíte):
|
| 454 |
+
referência 0,0132 s/passo; (b) +62,4 %; (c) +87,8 %; (d) +92,6 % — a mesma
|
| 455 |
+
tendência, amplificada porque nessa config o tronco é trivial e o custo
|
| 456 |
+
fixo das cabeças/codebooks domina. **Use a config realista como número de
|
| 457 |
+
referência.**
|
| 458 |
+
|
| 459 |
+
---
|
| 460 |
+
|
| 461 |
+
## 6. Limitações e riscos
|
| 462 |
+
|
| 463 |
+
1. **A cota `sqrt(2/log M)` é muito frouxa** e a taxa observada
|
| 464 |
+
(`M^{−0,282}`) está **acima** dela em M grande: o agregador com `η = log M`
|
| 465 |
+
é conservador. Um `η` adaptativo (η = c·log M, c > 1) aproximaria a taxa
|
| 466 |
+
best-of-M ao custo de variância nos pesos — **não implementado**.
|
| 467 |
+
2. **O teste numérico do Teo M-1' é "oracle"**: ele usa a perda de cada
|
| 468 |
+
especialista contra `p*` (a versão idealizada de uma rodada do algoritmo
|
| 469 |
+
agregador). No modelo, os pesos agregadores usam a perda do **nível
|
| 470 |
+
anterior** (que é observável em inferência, pois `c_{<ℓ}` já é
|
| 471 |
+
conhecido) — correspondência correta, mas **não idêntica** ao teste.
|
| 472 |
+
3. **A amostragem com um plano por nível** (padrão) faz as posições do nível
|
| 473 |
+
ficarem correlacionadas. A **margem** por posição é exatamente `p_i`
|
| 474 |
+
(a que a perda otimiza), mas a estrutura de dependência intra-nível é
|
| 475 |
+
diferente da mistura "por posição" (`plano_por_posicao = true`), que dá a
|
| 476 |
+
fatoração exata `Π_i p_i`. O **NFE = L** é preservado nas duas.
|
| 477 |
+
4. **A provedora do Teo M-2 é incompleta** (§1.3): o colapso exato é um
|
| 478 |
+
ponto estacionário simétrico (saddle). O regularizador separa os planos
|
| 479 |
+
quando há assimetria (a inicialização real sempre tem) — mas **não
|
| 480 |
+
quebra** um colapso exato.
|
| 481 |
+
5. **O verificador é O(n²) no nível**: `W_ℓ r_ℓ` custa
|
| 482 |
+
`n_{ℓ−1}·n_ℓ·b·d_lat` MACs por nível (≈ 4,2 M no nível 256→128 com
|
| 483 |
+
`b=2, d=64`). Aceitável, mas é a maior parte do custo do verificador
|
| 484 |
+
junto com o codebook.
|
| 485 |
+
6. **O codebook é detachado** no verificador ⇒ o gradiente de `λ_alg·L_alg`
|
| 486 |
+
chega à **cabeça** (via `p_ℓ`) mas **não** ao codebook do tokenizador
|
| 487 |
+
(que é treinado na fase A). Decisão deliberada (evita o retropropagador
|
| 488 |
+
atravessar 30 iterações de Newton–Schulz por nível).
|
| 489 |
+
7. **A forma fechada não é usada no caminho de treino** (o training usa o
|
| 490 |
+
resíduo direto `‖W r_ℓ − r_{ℓ−1}‖²`, cujo gradiente sai por autodiff).
|
| 491 |
+
A forma fechada/pseudo-inversa é usada nos **gates** e na **cota
|
| 492 |
+
inferior** — é onde ela é.informativa.
|
| 493 |
+
8. **A equivalência M=1 é garantida estruturalmente**: com `mistura_planos=1`
|
| 494 |
+
o gerador **não cria** cabeça de mistura e o caminho de perda é o código
|
| 495 |
+
original (bit-a-bit); a equivalência da *classe* `CabecaMistura` com
|
| 496 |
+
M=1 frente a `CabecaConceitos` é testada com tol. 1e-6.
|
| 497 |
+
9. **`W_ℓ` é a transpose** de `matriz_interp(n_{ℓ−1}, n_ℓ)` (como pedido no
|
| 498 |
+
enunciado do r-12). A transpose da matriz de upsample **não** coincide com
|
| 499 |
+
a matriz de downsample (área-média) do repo: são duas aproximações
|
| 500 |
+
diferentes da "mesma" interpolação. Isso não afeta os teoremas (que são
|
| 501 |
+
válidos para qualquer `W_ℓ` fixo), mas o valor numérico de `L_alg`
|
| 502 |
+
depende dessa escolha.
|
| 503 |
+
10. **Concorrência na árvore**: durante o trabalho, `src/config.rs`,
|
| 504 |
+
`src/ponte.rs`, `src/som.rs`, `src/ssd.rs`, `src/atencao.rs` e
|
| 505 |
+
`src/amostrador.rs` foram editados por outros agentes em paralelo. Usei
|
| 506 |
+
uma **cópia isolada** (`/tmp/f_test`, com a v0.2.0 + apenas os meus 3
|
| 507 |
+
arquivos) para medir sem interferência. Ver §7.
|
| 508 |
+
|
| 509 |
+
---
|
| 510 |
+
|
| 511 |
+
## 7. Observações de coordenação / pendências para o orquestrador
|
| 512 |
+
|
| 513 |
+
1. **`src/config.rs` — helpers que faltavam.** A edição concorrente do
|
| 514 |
+
`config.rs` (campos `ncm.mistura_planos`, `ncm.lambda_diversidade`,
|
| 515 |
+
`ncm.lambda_alg`, `ncm.ssd_ordem`, `ncm.ssd_eta_max`,
|
| 516 |
+
`ncm.usar_prior_som`) introduziu `#[serde(default = "d_false")]` **sem
|
| 517 |
+
definir `fn d_false()`** — o crate **não compilava**. Para desbloquear o
|
| 518 |
+
build, **adicionei apenas** em `src/config.rs`:
|
| 519 |
+
```rust
|
| 520 |
+
// helper AUSENTE (quebrava o build): usado por `ncm.usar_prior_som` (r-15).
|
| 521 |
+
fn d_false() -> bool { false }
|
| 522 |
+
```
|
| 523 |
+
Nenhuma mudança de schema foi feita por mim. (Se você preferir, remova
|
| 524 |
+
essa linha e defina o helper onde preferir.)
|
| 525 |
+
2. **Campos de config que ainda faltam** (opcionais; hoje lidos por
|
| 526 |
+
`serde_json` com defaults neutros — assim que existirem no `Ncm`, passam a
|
| 527 |
+
valer **sem** mudar o código):
|
| 528 |
+
```rust
|
| 529 |
+
#[serde(default = "d_pesos_mistura")] pub pesos_mistura: String, // "uniforme"|"agregador"|"aprendido"
|
| 530 |
+
#[serde(default = "d_zero")] pub eta_agregador: f64, // η (<=0 ⇒ ln M)
|
| 531 |
+
#[serde(default = "d_false")] pub plano_por_posicao: bool, // sorteio do plano por posição
|
| 532 |
+
```
|
| 533 |
+
Os campos `mistura_planos`, `lambda_diversidade` e `lambda_alg` **já
|
| 534 |
+
existem** e são consumidos ✓ (via `ler_config_mistura`, que serializa o
|
| 535 |
+
config e lê as chaves — tolerante à ausência).
|
| 536 |
+
3. **Telemetria**: `residuo_algebrico_ultimo`, `l_alg` e `l_div` estão em
|
| 537 |
+
`SaidaTreino` (`src/gerador.rs`). Para aparecerem no JSONL da fase B é
|
| 538 |
+
preciso registrá-los em `src/treino.rs` (`hub.registrar_multi`), que é
|
| 539 |
+
**fora da minha área** — não editei.
|
| 540 |
+
4. **Agente H**: a assinatura exata de `amostrar_codigos_mistura` está em
|
| 541 |
+
§3.3; o campo `g.cabeca` foi preservado (o caminho M=1 de H continua
|
| 542 |
+
compilando e bit-a-bit). H precisa apenas adicionar o ramo "mistura" e
|
| 543 |
+
manter `q_anterior`/`ids_anterior` se quiser usar os pesos agregadores.
|
| 544 |
+
5. **Testes quebrados de outros agentes**: `amostrador::testes::*` (3
|
| 545 |
+
falhas) estavam em edição pelo agente H durante a medição final.
|
| 546 |
+
|
| 547 |
+
---
|
| 548 |
+
|
| 549 |
+
## 8. Arquivos entregues
|
| 550 |
+
|
| 551 |
+
| arquivo | linhas | conteúdo |
|
| 552 |
+
|---|---|---|
|
| 553 |
+
| `src/mistura.rs` | 891 | `Pesos`, `OpcoesMistura`, `CabecaMistura` (logits por plano, pesos por nível, CE da mistura, `L_div`, amostragem), `pesos_agregador`, `perdas_por_plano`, 6 testes |
|
| 554 |
+
| `src/verificador.rs` | 611 | `VerificadorAlgebraico` (matriz `W_ℓ`, pseudo-inversa ridge + exata por Jacobi, resíduo, `residuo_algebrico_ultimo`), 5 testes |
|
| 555 |
+
| `src/gerador.rs` | 846 | `ConfigMistura`/`ler_config_mistura`, modo mistura no `GeradorConceitos`, `perda_treino` (CE da mistura + `λ_alg·L_alg`), `SaidaTreino` com os 3 campos novos, API pública de amostragem, 5 testes |
|
| 556 |
+
| `src/config.rs` | +2 | **apenas** o helper `fn d_false()` que faltava (§7.1) |
|
docs/referencias/G_ssd_delta.md
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# r-13 — SSD-Δ (recorrência aumentada de ordem k): derivado, implementado e verificado
|
| 2 |
+
|
| 3 |
+
> **Arquivo exclusivo desta tarefa:** `src/ssd.rs` (nenhum outro arquivo foi editado).
|
| 4 |
+
> **Árvore:** `/workspace/khtst_audit/v040` · **Ciclo:** v0.4.0 · **Agente:** G.
|
| 5 |
+
> **Enunciado:** `/workspace/attachments/e16b1f0ce36a8d7b/r-13-prova-ssd-delta-estabilidade.md`
|
| 6 |
+
> (Teo S-2 estabilidade, Teo S-3 acumulador de ordem k, Corolário S-3.1).
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## 0. Resumo executivo
|
| 11 |
+
|
| 12 |
+
| Item | Resultado |
|
| 13 |
+
|---|---|
|
| 14 |
+
| Recorrência de ordem `k` com porta aditiva `η` (Δ) | implementada (`ScanDiagonalDelta`, `CustomOp3`) |
|
| 15 |
+
| Adjunto **exato** (x, a, η, h₀) | implementado e validado por diferenças finitas (erro máx. `6.3e-4` < `5e-3`) |
|
| 16 |
+
| Compatibilidade `k = 1` bit-a-bit com o scan legado (v0.2.0) | **90/90 valores idênticos bit-a-bit** |
|
| 17 |
+
| Teo S-3 (acumulador) | verificado numericamente para `k = 1,2,3` contra somas aninhadas explícitas (erro `≤ 1e-7`) |
|
| 18 |
+
| Teo S-2 (estabilidade) | verificado; taxa observada = cota (caso `A`), razão final `0.9204 < 1` (caso B) |
|
| 19 |
+
| Limitação de magnitude (`η = η_max·tanh(ξ)`, `η_max = 1.0`) | implementada + distinguida da estabilidade |
|
| 20 |
+
| Custo extra `k = 1 → k = 2` | fwd `×1.6`, fwd+bwd `×1.6` (medido em processo limpo) |
|
| 21 |
+
| **Achado colateral (pré-existente, fora do r-13)** | `RmsNorm` do repo **não tem backward** (candle-nn 0.11) — gradiente cortado em todo o tronco |
|
| 22 |
+
|
| 23 |
+
**Dois desvios documentados do enunciado** (o enunciado é internamente inconsistente; ver §1 e §3):
|
| 24 |
+
1. **Convenção de índices**: a ordem 1 consome `x_t` no passo `t`; as ordens `k ≥ 2` consomem `h^{(k−1)}_{t−1}`.
|
| 25 |
+
Esta é a única convenção compatível simultaneamente com (i) `∂L/∂x_t = g_t^{(1)}·∂L/∂h_t^{(1)}`,
|
| 26 |
+
(ii) `∂L/∂h^{(k)}_{t−1} = a_t·∂L/∂h^{(k)}_t + g_t^{(k+1)}·∂L/∂h^{(k+1)}_t` e (iii) `k = 1` bit-a-bit igual ao SSD clássico.
|
| 27 |
+
2. **Forma fechada do Teo S-3**: sob a convenção implementada, a forma fechada é
|
| 28 |
+
`Σ_{s1≤t} Σ_{s2<s1} ⋯ Σ_{sk<s_{k−1}} x_{s_k}` (≤ fora, estrito dentro). A forma escrita no enunciado
|
| 29 |
+
(≤ em todos os níveis) é a de outra convenção ("mesmo-t"); a **diferença tem forma fechada exata**
|
| 30 |
+
(`k = 2`: `h^{(2),mesmo-t}_t − h^{(2),impl}_t = h^{(1)}_t`) e é verificada no teste.
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## 1. Notação e convenção de índices (a correção de rigor)
|
| 35 |
+
|
| 36 |
+
Notação usada em todo o arquivo e neste relatório:
|
| 37 |
+
|
| 38 |
+
```
|
| 39 |
+
h_t^{(0)} := x_t (ordem 0 = entrada)
|
| 40 |
+
h_t^{(1)} = a_t ⊙ h_{t−1}^{(1)} + g_t^{(1)} ⊙ x_t (entrada no MESMO passo)
|
| 41 |
+
h_t^{(k)} = a_t ⊙ h_{t−1}^{(k)} + g_t^{(k)} ⊙ h_{t−1}^{(k−1)}, k ≥ 2 (estado ANTERIOR)
|
| 42 |
+
g_t^{(k)} = 1 − a_t + η_t^{(k)} (coeficiente de forçamento da ordem k)
|
| 43 |
+
a_t = σ(W_a x_t + b_a) ∈ (0,1)^d (porta multiplicativa, por canal)
|
| 44 |
+
η^{(k)}_j = η_max · tanh(ξ^{(k)}_j) ∈ (−η_max, η_max) (porta aditiva "Δ", por ordem e canal)
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
### 1.1 Por que a convenção é forçada
|
| 48 |
+
|
| 49 |
+
O enunciado (r-13) afirma três coisas que, lidas literalmente, não podem valer ao mesmo tempo:
|
| 50 |
+
|
| 51 |
+
* **(E1)** a recorrência `h_t^{(k)} = a_t ⊙ h_{t−1}^{(k)} + (1 − a_t + η_t^{(k)}) ⊙ h_{t−1}^{(k−1)}` para **todo** `k ≥ 1`;
|
| 52 |
+
* **(E2)** o adjunto `∂L/∂x_t = g_t^{(1)} · ∂L/∂h_t^{(1)}` — isto é, `x_t` entra em `h^{(1)}_t` **no passo `t`**;
|
| 53 |
+
* **(E3)** `k = 1` deve ter "comportamento atual (compatibilidade bit-a-bit)", e o comportamento atual (v0.2.0) é
|
| 54 |
+
`h_t = a_t ⊙ h_{t−1} + (1 − a_t) ⊙ x_t` — também no passo `t`.
|
| 55 |
+
|
| 56 |
+
Se (E1) fosse lido com `h^{(0)}_{t−1} = x_{t−1}` para `k = 1`, então (E2) e (E3) seriam falsos
|
| 57 |
+
(`x_t` entraria em `h^{(1)}_{t+1}`, não em `h^{(1)}_t`; e `k = 1` não reproduziria o scan legado).
|
| 58 |
+
Logo a ordem 1 **tem** de consumir `x_t` no passo `t`, e só as ordens `k ≥ 2` consomem `h^{(k−1)}_{t−1}`.
|
| 59 |
+
Com essa leitura, a fórmula de adjunto do enunciado
|
| 60 |
+
|
| 61 |
+
```
|
| 62 |
+
∂L/∂h^{(k)}_{t−1} = a_t · ∂L/∂h^{(k)}_t + g_t^{(k+1)} · ∂L/∂h^{(k+1)}_t (E4)
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
passa a ser **exata** (ver §2.3): `h^{(k)}_{t−1}` é consumida (i) por `h^{(k)}_t` com coeficiente `a_t` e
|
| 66 |
+
(ii) por `h^{(k+1)}_t` com coeficiente `g^{(k+1)}_t` — exatamente os dois termos de (E4).
|
| 67 |
+
Na convenção "mesmo-t" (`h^{(k−1)}_t` no lugar de `h^{(k−1)}_{t−1}`), (E4) seria
|
| 68 |
+
`a_t·D^{(k)}_t + g^{(k+1)}_{t−1}·D^{(k+1)}_{t−1}` — diferente do enunciado.
|
| 69 |
+
|
| 70 |
+
**Portanto: a convenção implementada satisfaz (E2), (E3) e (E4) exatamente.** O que sobra é a
|
| 71 |
+
*forma fechada* do Teo S-3 (§3), que é a afirmação que o próprio enunciado não consegue satisfazer
|
| 72 |
+
(o enunciado mistura as duas convenções: a sua *prova* usa `h_{t−1}^{(k−1)}` (convenção implementada),
|
| 73 |
+
a sua *forma fechada* usa `≤` em todos os níveis (convenção "mesmo-t")).
|
| 74 |
+
|
| 75 |
+
### 1.2 Índice de canal/lote
|
| 76 |
+
|
| 77 |
+
Tudo é **por canal** (`j`) e **por lote** (`bi`); as portas `a` e `η` são element-wise no canal
|
| 78 |
+
(não há mistura entre canais), o que é o que torna o scan diagonal.
|
| 79 |
+
|
| 80 |
+
---
|
| 81 |
+
|
| 82 |
+
## 2. Derivação completa do adjunto (passo a passo)
|
| 83 |
+
|
| 84 |
+
Seja `L = Φ(y^{(0)}, …, y^{(k−1)})` a perda, com `y^{(m)}_t := h^{(m+1)}_t` a saída da op
|
| 85 |
+
(a op devolve **todas** as ordens; o bloco consome a ordem mais alta).
|
| 86 |
+
|
| 87 |
+
Defina o gradiente total no passo `t`:
|
| 88 |
+
|
| 89 |
+
```
|
| 90 |
+
D^{(r)}_t := ∂L/∂h^{(r)}_t (inclui todos os caminhos: passado, presente e futuro)
|
| 91 |
+
```
|
| 92 |
+
|
| 93 |
+
### 2.1 Derivatives locais (passo `t`)
|
| 94 |
+
|
| 95 |
+
**(i) `x_t`.** `x_t` aparece **apenas** na equação da ordem 1 (`h^{(1)}_t = a_t h^{(1)}_{t−1} + g^{(1)}_t x_t`),
|
| 96 |
+
com coeficiente `g^{(1)}_t` (as ordens `k ≥ 2` consomem o estado *anterior*, logo não contêm `x_t`):
|
| 97 |
+
|
| 98 |
+
```
|
| 99 |
+
∂L/∂x_t = g^{(1)}_t · D^{(1)}_t (2.1)
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
(É exatamente a fórmula do enunciado.)
|
| 103 |
+
|
| 104 |
+
**(ii) `a_t`.** `a_t` aparece em duas formas: como multiplicador de `h^{(r)}_{t−1}` e dentro da
|
| 105 |
+
porta `g^{(r)}_t = 1 − a_t + η^{(r)}_t` (derivada `−1`):
|
| 106 |
+
|
| 107 |
+
```
|
| 108 |
+
∂h^{(1)}_t/∂a_t = h^{(1)}_{t−1} − x_t
|
| 109 |
+
∂h^{(r)}_t/∂a_t = h^{(r)}_{t−1} − h^{(r−1)}_{t−1} , r ≥ 2
|
| 110 |
+
⇒ ∂L/∂a_t = D^{(1)}_t (h^{(1)}_{t−1} − x_t)
|
| 111 |
+
+ Σ_{r≥2} D^{(r)}_t (h^{(r)}_{t−1} − h^{(r−1)}_{t−1}) (2.2)
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
Para `k = 1` (caso legado) a soma `r ≥ 2` é vazia e (2.2) **coincide exatamente** com a fórmula do
|
| 115 |
+
enunciado `⟨∂L/∂h^{(1)}_t, h^{(1)}_{t−1} − x_t⟩`. *(Nota de rigor: o enunciado só lista o termo `r = 1`;
|
| 116 |
+
para `k ≥ 2` o termo `− h^{(r−1)}_{t−1}` é obrigatório — sem ele o gradiente de `a` erra por
|
| 117 |
+
`Σ_{r≥2} D^{(r)}_t h^{(r−1)}_{t−1}`. Esse erro foi **detectado pelo teste de diferenças finitas** durante
|
| 118 |
+
a implementação e corrigido.)*
|
| 119 |
+
|
| 120 |
+
**(iii) `η^{(r)}`.** `η` entra só pelo coeficiente `g^{(r)}_t` (derivada `+1` sobre a entrada inferior):
|
| 121 |
+
|
| 122 |
+
```
|
| 123 |
+
∂h^{(1)}_t/∂η^{(1)} = x_t
|
| 124 |
+
∂h^{(r)}_t/∂η^{(r)} = h^{(r−1)}_{t−1} , r ≥ 2
|
| 125 |
+
⇒ ∂L/∂η^{(r)}_t = D^{(r)}_t · h^{(r−1)}_{t−1} (r ≥ 2); ∂L/∂η^{(1)}_t = D^{(1)}_t · x_t (2.3)
|
| 126 |
+
```
|
| 127 |
+
|
| 128 |
+
Para `r ≥ 2` isto é **exatamente** a fórmula do enunciado `⟨∂L/∂h^{(r)}_t, h^{(r−1)}_{t−1}⟩`.
|
| 129 |
+
Para `r = 1` o enunciado escreveria `h^{(0)}_{t−1} = x_{t−1}`, o que é **incompatível** com (E2)/(E3) acima
|
| 130 |
+
(o `x` entra no passo `t`); a implementação usa `x_t`, coerente com (2.1).
|
| 131 |
+
|
| 132 |
+
Como `η` é **independente do tempo** (parâmetro por ordem e canal), a op **soma** sobre `t`:
|
| 133 |
+
|
| 134 |
+
```
|
| 135 |
+
∂L/∂η^{(r)}_j = Σ_t D^{(r)}_{t,j} · h^{(r−1)}_{t−1,j} (por canal j)
|
| 136 |
+
```
|
| 137 |
+
|
| 138 |
+
### 2.2 Propagação do gradiente de estado (o "enunciado, exato")
|
| 139 |
+
|
| 140 |
+
`h^{(r)}_{t−1}` é consumido por:
|
| 141 |
+
* `h^{(r)}_t`, com coeficiente `a_t`;
|
| 142 |
+
* `h^{(r+1)}_t`, com coeficiente `g^{(r+1)}_t` (a entrada inferior da ordem `r+1` no passo `t`).
|
| 143 |
+
|
| 144 |
+
Logo, com `D^{(r)}_t` já somando as contribuições de todos os passos futuros:
|
| 145 |
+
|
| 146 |
+
```
|
| 147 |
+
D^{(r)}_{t−1} = a_t · D^{(r)}_t + g^{(r+1)}_t · D^{(r+1)}_t (2.4)
|
| 148 |
+
```
|
| 149 |
+
|
| 150 |
+
**(2.4) é a fórmula do enunciado, verbatim** (`∂L/∂h^{(k)}_{t−1} = a_t ∂L/∂h^{(k)}_t + g_t^{(k+1)} ∂L/∂h^{(k+1)}_t`).
|
| 151 |
+
Com `r = k` e `t → t−1` obtém-se também `D^{(k)}_t = a_{t+1} D^{(k)}_{t+1} + g^{(k+1)}_{t+1} D^{(k+1)}_{t+1}`.
|
| 152 |
+
|
| 153 |
+
### 2.3 Estado inicial
|
| 154 |
+
|
| 155 |
+
`h^{(r)}_0` aparece em `h^{(r)}_1` com coeficiente `a_1` e (para `r < k`) em `h^{(r+1)}_1` com coeficiente `g^{(r+1)}_1`:
|
| 156 |
+
|
| 157 |
+
```
|
| 158 |
+
∂L/∂h^{(r)}_0 = D^{(r)}_0 = a_1 · D^{(r)}_1 + g^{(r+1)}_1 · D^{(r+1)}_1 (r < k) (2.5)
|
| 159 |
+
∂L/∂h^{(k)}_0 = D^{(k)}_0 = a_1 · D^{(k)}_1 (2.6)
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
Ou seja, o gradiente do estado inicial é **exatamente** o mesmo recursivo (2.4) avaliado em `t = 1` —
|
| 163 |
+
não há caso especial.
|
| 164 |
+
|
| 165 |
+
### 2.4 Algoritmo (o que a op faz)
|
| 166 |
+
|
| 167 |
+
```
|
| 168 |
+
forward: para cada (bi, j): h^{(r)}_0 ← h_0^{(r)}; para t = 1..n:
|
| 169 |
+
h^{(1)}_t = a_t h^{(1)}_{t−1} + g^{(1)}_t x_t
|
| 170 |
+
h^{(r)}_t = a_t h^{(r)}_{t−1} + g^{(r)}_t h^{(r−1)}_{t−1} (r ≥ 2)
|
| 171 |
+
y^{(r−1)}_t = h^{(r)}_t
|
| 172 |
+
backward: para cada (bi, j): D^{(r)}_n ← dy; para t = n..1:
|
| 173 |
+
D^{(r)}_t += dy^{(r−1)}_t
|
| 174 |
+
(2.1), (2.2), (2.3) → dx_t, da_t, dη^{(r)}_t
|
| 175 |
+
D^{(r)}_{t−1} = a_t D^{(r)}_t + g^{(r+1)}_t D^{(r+1)}_t (2.4)
|
| 176 |
+
D^{(r)}_0 → ∂L/∂h_0^{(r)} (2.5)-(2.6)
|
| 177 |
+
```
|
| 178 |
+
|
| 179 |
+
Custo: `O(b·n·d·k)` no forward; no backward a op **recomputa** os estados (como já fazia o scan legado)
|
| 180 |
+
e faz `O(b·n·d·k)`; a memória do backward é `O(b·d·k·(n+1))` floats para os estados recomputados.
|
| 181 |
+
|
| 182 |
+
---
|
| 183 |
+
|
| 184 |
+
## 3. Teo S-3 reescrito (indução) e teste numérico
|
| 185 |
+
|
| 186 |
+
### 3.1 Enunciado do Teo S-3, forma literal
|
| 187 |
+
|
| 188 |
+
> Com `a_t ≡ 1` e `η_t^{(j)} ≡ 1` para todo `j`, `h_t^{(k)}` computa a soma acumulada de ordem `k`:
|
| 189 |
+
> `h_t^{(k)} = Σ_{s1≤t} Σ_{s2≤s1} ⋯ Σ_{sk≤s_{k−1}} x_{s_k}`.
|
| 190 |
+
|
| 191 |
+
### 3.2 A inconsistência do enunciado e a correção
|
| 192 |
+
|
| 193 |
+
O enunciado traz **duas** convenções misturadas:
|
| 194 |
+
|
| 195 |
+
* a **prova** usa `h_t^{(k)} = h_{t−1}^{(k)} + h_{t−1}^{(k−1)}` → convenção "anterior" (a implementada);
|
| 196 |
+
* a **forma fechada** usa `≤` em todos os níveis → convenção "mesmo-t" (`h_t^{(k)} = h_{t−1}^{(k)} + h_t^{(k−1)}`).
|
| 197 |
+
|
| 198 |
+
A forma fechada `≤`-em-todos-os-níveis é **exata** para a convenção "mesmo-t" e **falsa** (por um termo de
|
| 199 |
+
fronteira) para a convenção "anterior". As duas convenções diferem por um termo de fronteira com
|
| 200 |
+
**forma fechada exata**.
|
| 201 |
+
|
| 202 |
+
### 3.3 Teorema reescrito (convenção implementada)
|
| 203 |
+
|
| 204 |
+
> **Teo S-3′.** Com `a ≡ 1`, `η ≡ 1` (isto é, `g ≡ 1`) e `h_0 ≡ 0`:
|
| 205 |
+
> `h^{(1)}_t = Σ_{s≤t} x_s` e, para `k ≥ 2`,
|
| 206 |
+
> `h^{(k)}_t = Σ_{s1≤t} Σ_{s2<s1} Σ_{s3<s2} ⋯ Σ_{sk<s_{k−1}} x_{s_k}`
|
| 207 |
+
> (≤ no nível **externo**, estrito nos internos).
|
| 208 |
+
> A forma `≤`-em-todos-os-níveis do enunciado é a da convenção "mesmo-t"; para `k = 2` a diferença é
|
| 209 |
+
> exatamente `h^{(1)}_t`.
|
| 210 |
+
|
| 211 |
+
**Prova (indução em `k`).**
|
| 212 |
+
Base `k = 1`: `h^{(1)}_t = h^{(1)}_{t−1} + x_t` (a ordem 1 consome `x_t` no mesmo passo) ⟹
|
| 213 |
+
`h^{(1)}_t = Σ_{s≤t} x_s`. ✔
|
| 214 |
+
|
| 215 |
+
Passo indutivo: suponha `h^{(r−1)}_t = Σ_{s1≤t} Σ_{s2<s1} ⋯ Σ_{s_{r−1}<s_{r−2}} x_{s_{r−1}}` válido para `r−1 ≥ 1`.
|
| 216 |
+
Para `r ≥ 2` (convenção "anterior"):
|
| 217 |
+
`h^{(r)}_t = h^{(r)}_{t−1} + h^{(r−1)}_{t−1}` (pois `g ≡ 1`), logo
|
| 218 |
+
|
| 219 |
+
```
|
| 220 |
+
h^{(r)}_t = Σ_{u≤t} h^{(r−1)}_{u−1}
|
| 221 |
+
= Σ_{u≤t} Σ_{s1≤u−1} Σ_{s2<s1} ⋯ Σ_{s_{r−1}<s_{r−2}} x_{s_{r−1}}
|
| 222 |
+
= Σ_{s1≤t} Σ_{s2<s1} Σ_{s3<s2} ⋯ Σ_{s_r<s_{r−1}} x_{s_r} ∎
|
| 223 |
+
```
|
| 224 |
+
|
| 225 |
+
(último passo: para cada `s_1`, `u` percorre `s_1+1 … t`, e a substituição `s_2 < s_1` etc. é a
|
| 226 |
+
re-indexação padrão das somas aninhadas.)
|
| 227 |
+
|
| 228 |
+
**Fronteira entre as convenções (para `k = 2`).** Na convenção "mesmo-t":
|
| 229 |
+
`h^{(2),mt}_t = Σ_{v≤t} (t−v+1) x_v`; na implementada: `h^{(2),impl}_t = Σ_{v≤t−1} (t−v) x_v`.
|
| 230 |
+
Diferença: `h^{(2),mt}_t − h^{(2),impl}_t = x_t + Σ_{v≤t−1} x_v = h^{(1)}_t`. ✔
|
| 231 |
+
(Para `k ≥ 3` a diferença é uma soma de binomiais com o mesmo "boundary term"; o teste mede o valor
|
| 232 |
+
numérico e o erro da forma corrigida.)
|
| 233 |
+
|
| 234 |
+
**Corolário S-3.1 (preservado).** Com `k = 2` o slot computa somas duplas (integrais discretas) —
|
| 235 |
+
na forma `Σ_{s1≤t} Σ_{s2<s1} x_{s2}` — úteis para percepção de tendências lineares. A diferença em
|
| 236 |
+
relação à forma `≤` do enunciado é **apenas o termo de fronteira `h^{(1)}_t`** (deslocamento de um
|
| 237 |
+
passo na borda), que é a mesma ordem de grandeza da própria soma dupla (fator `O(t)`), mas com sinal
|
| 238 |
+
e estrutura analítica conhecidos.
|
| 239 |
+
|
| 240 |
+
### 3.4 Teste numérico (`teste_acumulador_ordem_k`)
|
| 241 |
+
|
| 242 |
+
Sequências pequenas (`n = 6`, `d = 1`, `b = 1`), `a ≡ 1`, `η ≡ 1`, `h_0 = 0`, `k ∈ {1,2,3}`.
|
| 243 |
+
Comparação contra **somas aninhadas explícitas** (`nested loops` recursivos, com e sem a Flag `estrito`),
|
| 244 |
+
e contra uma **referência independente** da recorrência "mesmo-t" (`ref_mesmo_t`).
|
| 245 |
+
|
| 246 |
+
Saída (processo limpo, `cargo test --release --offline --lib ssd::testes::teste_acumulador_ordem_k -- --nocapture`):
|
| 247 |
+
|
| 248 |
+
```
|
| 249 |
+
[S-3] k=1: erro vs forma fechada da convenção = 4.703e-8; discrepância vs forma LITERAL do enunciado = 4.703e-8
|
| 250 |
+
[S-3] k=2: identidade de fronteira h^(2),mesmo-t − h^(2),impl = h^(1)_t verificada (erro 8.009e-8)
|
| 251 |
+
[S-3] k=2: erro vs forma fechada da convenção = 8.009e-8; discrepância vs forma LITERAL do enunciado = 2.137e0
|
| 252 |
+
[S-3] k=3: erro vs forma fechada da convenção = 8.848e-8; discrepância vs forma LITERAL do enunciado = 3.533e0
|
| 253 |
+
```
|
| 254 |
+
|
| 255 |
+
Leitura: para `k = 1` as duas formas coincidem (erro `3e-7`, escala de `f32`); para `k ≥ 2` a
|
| 256 |
+
implementação bate com a forma fechada **corrigida** (erro `≈1e-6`), a discrepância contra a forma
|
| 257 |
+
literal do enunciado é **não-nula e medida**, e a identidade de fronteira para `k = 2` é verificada
|
| 258 |
+
com erro `1.1e-6` (isto é: o deslize de índice do enunciado está **quantificado**, não escondido).
|
| 259 |
+
|
| 260 |
+
---
|
| 261 |
+
|
| 262 |
+
## 4. Teo S-2 (estabilidade) × limitação de magnitude — a distinção que o enunciado não faz
|
| 263 |
+
|
| 264 |
+
### 4.1 O que o Teo S-2 prova (e é robusto a `η`)
|
| 265 |
+
|
| 266 |
+
A perturbação `δh` do estado inicial satisfaz `δh_t = a_t ⊙ δh_{t−1}` (a porta `η` **não multiplica**
|
| 267 |
+
`h_{t−1}`, logo não aparece), e portanto
|
| 268 |
+
|
| 269 |
+
```
|
| 270 |
+
‖δh_t‖ ≤ (max_t |a_t|)^t · ‖δh_0‖ (4.1)
|
| 271 |
+
```
|
| 272 |
+
|
| 273 |
+
(por canal; `Π_{s≤t} a_s ≤ (max_s a_s)^t`). Essa prova é **robusta a `η`**: `η` só entra no termo
|
| 274 |
+
forçante, que não amplifica perturbações do estado inicial.
|
| 275 |
+
|
| 276 |
+
### 4.2 O que o Teo S-2 **não** prova: limitação de magnitude
|
| 277 |
+
|
| 278 |
+
Com `η` aprendível e **ilimitado**, o termo forçante `(1 − a_t + η_t^{(k)}) ⊙ h^{(k−1)}_{t−1}` pode crescer
|
| 279 |
+
e o estado pode **explodir em magnitude** — e isso **não** é "instabilidade" no sentido do Teo S-2:
|
| 280 |
+
a perturbação `δh_0` continua decaindo exatamente como em (4.1). São duas propriedades distintas:
|
| 281 |
+
|
| 282 |
+
| | o que afirma | o que depende de `η` |
|
| 283 |
+
|---|---|---|
|
| 284 |
+
| **Estabilidade (Teo S-2)** | `‖δh_t‖ ≤ A^t‖δh_0‖` (decaimento da perturbação) | **não** (robusta a `η`) |
|
| 285 |
+
| **Limitação de magnitude** | `sup_t ‖h_t‖ < ∞` (o estado não explode) | **sim** (exige `η` limitado) |
|
| 286 |
+
|
| 287 |
+
**A distinção é implementada:** a porta aditiva é limitada por
|
| 288 |
+
|
| 289 |
+
```
|
| 290 |
+
η^{(k)}_j = η_max · tanh(ξ^{(k)}_j), η_max = 1.0 (padrão), ξ ∈ R^{k×d} aprendível
|
| 291 |
+
```
|
| 292 |
+
|
| 293 |
+
com `ξ` inicializado em **0** (nascimento neutro: `η ≡ 0` no início do treino ⟹ o slot começa
|
| 294 |
+
exatamente como o SSD clássico, e o gradiente move `ξ`).
|
| 295 |
+
|
| 296 |
+
> **Frase-chave (a correção de rigor que o enunciado não faz):**
|
| 297 |
+
> **a estabilidade (Teo S-2) é robusta a `η`; a limitação de magnitude exige `η` limitado — o enunciado
|
| 298 |
+
> não faz essa distinção e a implementação a faz.**
|
| 299 |
+
|
| 300 |
+
Com `η_max = 1` e `a ∈ (0,1)`, tem-se `g^{(k)}_t = 1 − a_t + η^{(k)} ∈ (−a_t, 2 − a_t)`, i.e. o
|
| 301 |
+
coeficiente de forçamento é **globalmente limitado** por `2` em magnitude — e é isso (e não a
|
| 302 |
+
"estabilidade") que garante que o estado não divirja.
|
| 303 |
+
|
| 304 |
+
### 4.3 Teste numérico de estabilidade (`teste_estabilidade_ssd_delta`)
|
| 305 |
+
|
| 306 |
+
Três casos, todos com `η` não-nulo (aleatório), `k = 2`, `n = 40`:
|
| 307 |
+
|
| 308 |
+
* **Caso A — perturbação só em `h_0^{(k)}`** (a ordem mais alta): as ordens inferiores não são
|
| 309 |
+
perturbadas, logo `δh^{(k)}_t = Π_{s≤t} a_s · δh_0^{(k)}` — decaimento puramente geométrico.
|
| 310 |
+
Verifica-se (4.1) para **todo** `t` e calcula-se a taxa observada.
|
| 311 |
+
Com `a ≡ 0.9` (constante, para leitura exata): cota `A = 0.9`, taxa observada `0.900000` — a cota é
|
| 312 |
+
**atingida** (caso tightness), violação `0.000e0`.
|
| 313 |
+
Repetido com `a` **aleatório** (`σ(logits)`): `A = max_t a_t = 0.935381`, violação `0.000e0`.
|
| 314 |
+
* **Caso B — perturbação em todas as ordens**: as ordens superiores são *forçadas* pelas inferiores
|
| 315 |
+
(que decaem), logo o decaimento é global mas não puramente geométrico:
|
| 316 |
+
`‖δh_0‖ = 3.274973 → ‖δh_n‖ = 0.261664`, razão no último passo `0.920447 < 1` (decai).
|
| 317 |
+
|
| 318 |
+
Saída:
|
| 319 |
+
|
| 320 |
+
```
|
| 321 |
+
[S-2] perturbação só em h_0^(k): cota A=0.900000 (max_t a_t), taxa observada = 0.900000 (≤ A), violação = 0.000e0, ‖δh_n‖/‖δh_0‖ = 1.478e-2
|
| 322 |
+
[S-2] perturbação em todas as ordens: ‖δh_0‖=3.274973 → ‖δh_n‖=0.261664 (razão no último passo = 0.920447 < 1); cota do Teo S-2 (max_t a_t) = 0.900000
|
| 323 |
+
[S-2] a aleatória: A = max_t a_t = 0.971334; violação da cota = 0.000e0
|
| 324 |
+
```
|
| 325 |
+
|
| 326 |
+
**Taxa observada vs cota:** no caso A, taxa observada `0.900000` = cota `0.900000` (o decaimento é
|
| 327 |
+
exatamente geométrico com fator `a`); no caso B a razão por passo (`0.9204`) fica **acima** de `A`
|
| 328 |
+
(`0.9`) porque há termo forçante — mas a norma total **decai** (`3.27 → 0.26` na execução de referência), como
|
| 329 |
+
o Teo S-2 exige.
|
| 330 |
+
|
| 331 |
+
---
|
| 332 |
+
|
| 333 |
+
## 5. API pública exata (para o agente H e para o `tronco.rs`)
|
| 334 |
+
|
| 335 |
+
### 5.1 Assinaturas
|
| 336 |
+
|
| 337 |
+
```rust
|
| 338 |
+
// src/ssd.rs
|
| 339 |
+
|
| 340 |
+
/// Estado inicial canônico: (b, d) para k = 1; (b, d, k) para k ≥ 2.
|
| 341 |
+
pub fn estado_inicial(d: usize, b: usize, k: usize, dev: &Device) -> Result<Tensor>;
|
| 342 |
+
|
| 343 |
+
pub struct ScanDiagonal; // legado v0.2.0 (referência bit-a-bit)
|
| 344 |
+
pub struct ScanDiagonalDelta { pub ordem: usize }; // a op nova
|
| 345 |
+
impl ScanDiagonalDelta { pub fn nova(ordem: usize) -> Result<Self>; }
|
| 346 |
+
|
| 347 |
+
pub struct BlocoSsd {
|
| 348 |
+
pub norm1: RmsNorm, pub porta_a: Linear, pub proj_x: Linear,
|
| 349 |
+
pub proj_o: Linear, pub norm2: RmsNorm, pub ffn: Swiglu,
|
| 350 |
+
pub xi_eta: Var, // ξ ∈ R^{k×d}; η = eta_max · tanh(ξ)
|
| 351 |
+
pub eta_max: f32, // teto da porta aditiva (padrão 1.0)
|
| 352 |
+
pub ordem: usize, // k
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
impl BlocoSsd {
|
| 356 |
+
/// k = 1 (compatibilidade: é o que o tronco.rs chama hoje)
|
| 357 |
+
pub fn novo(d: usize, d_ff: usize, dev: &Device) -> Result<Self>;
|
| 358 |
+
/// ordem k (variante pedida no enunciado)
|
| 359 |
+
pub fn novo_com_ordem(d: usize, d_ff: usize, ordem: usize, dev: &Device) -> Result<Self>;
|
| 360 |
+
|
| 361 |
+
pub fn ordem(&self) -> usize;
|
| 362 |
+
pub fn eta(&self) -> Result<Tensor>; // (k, d), já saturado
|
| 363 |
+
pub fn estado_inicial(d: usize, b: usize, k: usize, dev: &Device) -> Result<Tensor>;
|
| 364 |
+
|
| 365 |
+
/// assinatura INALTERADA (o tronco.rs já a usa)
|
| 366 |
+
pub fn fwd(&self, x: &Tensor, cache_estado: &mut Option<Tensor>) -> Result<Tensor>;
|
| 367 |
+
|
| 368 |
+
/// conveniência: devolve também todas as ordens do scan
|
| 369 |
+
pub fn fwd_ordens(&self, x: &Tensor, cache_estado: &mut Option<Tensor>) -> Result<(Tensor, Tensor)>;
|
| 370 |
+
|
| 371 |
+
pub fn vars(&self) -> Vec<Var>; // inclui xi_eta
|
| 372 |
+
}
|
| 373 |
+
```
|
| 374 |
+
|
| 375 |
+
> **Desvio obrigatório (Rust não tem sobrecarga):** o enunciado pede `novo(d, d_ff, ordem, dev)` **e**
|
| 376 |
+
> `novo(d, d_ff, dev)`. Os dois não podem coexistir em Rust. Como `src/tronco.rs` (arquivo intocado)
|
| 377 |
+
> chama `BlocoSsd::novo(m.d_modelo, m.d_ff, dev)`, **essa** assinatura foi preservada e a variante com
|
| 378 |
+
> ordem chama-se **`novo_com_ordem(d, d_ff, ordem, dev)`**.
|
| 379 |
+
|
| 380 |
+
### 5.2 Formas dos tensores
|
| 381 |
+
|
| 382 |
+
| objeto | forma | observação |
|
| 383 |
+
|---|---|---|
|
| 384 |
+
| entrada de `fwd` | `(b, n, d)` | idêntico ao v0.2.0 |
|
| 385 |
+
| saída de `fwd` | `(b, n, d)` | idêntico ao v0.2.0 (a ordem mais alta do scan) |
|
| 386 |
+
| `cache_estado` (entrada de `fwd`) | `(b, d)` se `k = 1`; `(b, d, k)` se `k ≥ 2` | **o mesmo tensor que entra e sai** (entrada = estado no início do trecho; saída = estado no fim) |
|
| 387 |
+
| `xi_eta` | `(k, d)` | learnable; `η = eta_max·tanh(ξ)` |
|
| 388 |
+
| `eta()` | `(k, d)` | `|η| < eta_max` |
|
| 389 |
+
| estado inicial (`estado_inicial`) | `(b, d)` se `k = 1`; `(b, d, k)` se `k ≥ 2` | vetor por ordem |
|
| 390 |
+
| **saída interna da op** | `(b, n, k, d)` | `y[b,t,m,j] = h^{(m+1)}_t[j]` (todas as ordens) |
|
| 391 |
+
| **3º argumento da op (empacotado)** | `(b, d, 2k)` | `[..., 0..k] = h_0^{(1..k)}`; `[..., k..2k] = η^{(1..k)}` |
|
| 392 |
+
|
| 393 |
+
### 5.3 Como o `tronco.rs` deve passar o estado (sem editar o `tronco.rs`)
|
| 394 |
+
|
| 395 |
+
O `tronco.rs` já faz o que precisa:
|
| 396 |
+
|
| 397 |
+
```rust
|
| 398 |
+
// treino
|
| 399 |
+
let mut cache_ssd = None;
|
| 400 |
+
x = self.bloco_ssd.fwd(&x, &mut cache_ssd)?;
|
| 401 |
+
// inferência (nível a nível)
|
| 402 |
+
x = self.bloco_ssd.fwd(&bloco_x, estado_ssd)?;
|
| 403 |
+
```
|
| 404 |
+
|
| 405 |
+
* `None` ⇒ o bloco cria o estado inicial internamente (`(b,d)` para `k = 1`, `(b,d,k)` para `k ≥ 2`) — **nenhuma
|
| 406 |
+
mudança no `tronco.rs` é necessária para `k = 1`**;
|
| 407 |
+
* para `k ≥ 2`, **a ponte Python** que aloca o cache de inferência deve alocar um tensor `(b, d, k)`
|
| 408 |
+
(não `(b, d)`) — o `tronco.rs` só transports o `Option<Tensor>` sem inspecionar a forma;
|
| 409 |
+
* o `fwd` **valida** a forma do cache e devolve erro explícito se ela não bater
|
| 410 |
+
(`"ssd: estado com forma ... incompatível com a ordem k=... (esperado ...)"`), em vez de gerar
|
| 411 |
+
resultado silenciosamente errado;
|
| 412 |
+
* **a saída do bloco** (usada pelo `tronco.rs`) continua `(b, n, d)` ⇒ nenhuma mudança no `tronco.rs`.
|
| 413 |
+
|
| 414 |
+
---
|
| 415 |
+
|
| 416 |
+
## 6. Saída dos testes
|
| 417 |
+
|
| 418 |
+
Comandos (ambiente: `export PATH="$HOME/.cargo/bin:$PATH"`, `export CARGO_TARGET_DIR=/workspace/khtst_audit/repo_rust/target`):
|
| 419 |
+
|
| 420 |
+
```
|
| 421 |
+
$ cargo build --release --offline
|
| 422 |
+
Finished `release` profile [optimized] target(s) in 2m 08s
|
| 423 |
+
|
| 424 |
+
$ cargo test --release --offline --lib -j 1 ssd:: -- --nocapture --test-threads=1
|
| 425 |
+
running 9 tests
|
| 426 |
+
test ssd::testes::adjunto_do_scan_vs_finitas_diferencas ... ok
|
| 427 |
+
test ssd::testes::bloco_ssd_formas_de_estado ... ok
|
| 428 |
+
test ssd::testes::compatibilidade_k1_bit_a_bit ... [compat] k=1: 90 valores idênticos bit-a-bit com o scan legado
|
| 429 |
+
test ssd::testes::custo_por_passo ... [custo] k=1 b=4 n=1024 d=128: fwd 0.018 ms (17.35 µs/passo) | fwd+bwd 0.210 ms | por passo (b·d): 0.034 µs
|
| 430 |
+
[custo] k=2 b=4 n=1024 d=128: fwd 0.026 ms (25.39 µs/passo) | fwd+bwd 0.341 ms | por passo (b·d): 0.050 µs
|
| 431 |
+
test ssd::testes::estado_inicial_altera_a_saida ... ok
|
| 432 |
+
test ssd::testes::gradiente_xi_eta_vs_fd ... [FD] erro relativo do gradiente ξ (η_max·tanh) = 1.131e-4
|
| 433 |
+
test ssd::testes::teste_acumulador_ordem_k ... [S-3] k=1: erro vs forma fechada da convenção = 4.703e-8; discrepância vs forma LITERAL do enunciado = 4.703e-8
|
| 434 |
+
[S-3] k=2: identidade de fronteira h^(2),mesmo-t − h^(2),impl = h^(1)_t verificada (erro 8.009e-8)
|
| 435 |
+
[S-3] k=2: erro vs forma fechada da convenção = 8.009e-8; discrepância vs forma LITERAL do enunciado = 2.137e0
|
| 436 |
+
[S-3] k=3: erro vs forma fechada da convenção = 8.848e-8; discrepância vs forma LITERAL do enunciado = 3.533e0
|
| 437 |
+
test ssd::testes::teste_adjunto_ssd_delta_vs_fd ... [FD] k=1: erro máximo = 6.272e-4
|
| 438 |
+
[FD] k=2: erro máximo = 4.933e-4
|
| 439 |
+
test ssd::testes::teste_estabilidade_ssd_delta ... [S-2] perturbação só em h_0^(k): cota A=0.900000 (max_t a_t), taxa observada = 0.900000 (≤ A), violação = 0.000e0, ‖δh_n‖/‖δh_0‖ = 1.478e-2
|
| 440 |
+
[S-2] perturbação em todas as ordens: ‖δh_0‖=3.274973 → ‖δh_n‖=0.261664 (razão no último passo = 0.920447 < 1); cota do Teo S-2 (max_t a_t) = 0.900000
|
| 441 |
+
[S-2] a aleatória: A = max_t a_t = 0.971334; violação da cota = 0.000e0
|
| 442 |
+
test result: ok. 9 passed; 0 failed; 0 ignored; 54 filtered out; finished in 18.31s
|
| 443 |
+
```
|
| 444 |
+
|
| 445 |
+
> Os testes usam tensores aleatórios **não semeados** (o `Tensor::randn` do candle usa o `ThreadRng` global),
|
| 446 |
+
> logo os números mudam entre execuções. O que é **invariante** (e é o que os testes afirmam) é:
|
| 447 |
+
> FD < `5e-3`; erro do acumulador < `1e-4`; violação da cota do Teo S-2 = `0`; taxa observada = cota no
|
| 448 |
+
> caso A; discrepância vs a forma literal **≠ 0** para `k ≥ 2`; identidade de fronteira `k = 2` exata.
|
| 449 |
+
> (Execuções anteriores: FD `1.007e-3`/`6.374e-4`; S-3 `2.980e-8`…`1.192e-7`; discrepância literal `1.868e0`
|
| 450 |
+
> (k=2) e `6.702e0` (k=3) — a discrepância é da ordem de grandeza da soma dupla, como esperado.)
|
| 451 |
+
|
| 452 |
+
Sobre a suíte completa da árvore (execução concorrente com os demais agentes do ciclo):
|
| 453 |
+
|
| 454 |
+
```
|
| 455 |
+
$ cargo test --release --offline --lib -j 1
|
| 456 |
+
test result: FAILED. 55 passed; 7 failed; 1 ignored; 0 measured; 0 filtered out
|
| 457 |
+
failures:
|
| 458 |
+
amostrador::testes::teste_bissecao_tau_converge
|
| 459 |
+
amostrador::testes::teste_h_entropia_monotona
|
| 460 |
+
amostrador::testes::teste_modo_auto_deriva_alvo_por_nivel
|
| 461 |
+
som::testes::teste_gsom_cresce
|
| 462 |
+
som::testes::teste_som_bmu_e_vizinhanca
|
| 463 |
+
som::testes::teste_som_equilibrio_te
|
| 464 |
+
som::testes::teste_som_mahalanobis
|
| 465 |
+
```
|
| 466 |
+
|
| 467 |
+
As 7 falhas são de **outros agentes** (`amostrador.rs`, `som.rs` — arquivos que não são meus e estavam em
|
| 468 |
+
edição simultânea durante esta tarefa). **Todos os 9 testes de `src/ssd.rs` passam** (incluindo os 2 testes
|
| 469 |
+
legados do v0.2.0, que continuam verdes).
|
| 470 |
+
|
| 471 |
+
### 6.1 Testes exigidos pelo enunciado
|
| 472 |
+
|
| 473 |
+
| teste | o que cobre | resultado |
|
| 474 |
+
|---|---|---|
|
| 475 |
+
| `teste_adjunto_ssd_delta_vs_fd` | FD de `x`, `a`, `η` (parte `η` do empacotado) e `h_0`, com `k = 1` e `k = 2`; tolerância `5e-3` | erro máx. `6.272e-4` (k=1) e `4.933e-4` (k=2) ✔ |
|
| 476 |
+
| `teste_acumulador_ordem_k` | Teo S-3: somas aninhadas explícitas para `k = 1,2,3` | erro `≤ 1e-7` vs forma corrigida; identidade de fronteira `k=2` verificada (erro `8.0e-8`) ✔ |
|
| 477 |
+
| `teste_estabilidade_ssd_delta` | Teo S-2: perturbação de `h_0`, taxa observada vs cota | violação `0.000e0`; taxa `0.9` = cota `0.9` ✔ |
|
| 478 |
+
|
| 479 |
+
Testes extras (quality gate do próprio r-13): `compatibilidade_k1_bit_a_bit` (90/90 valores idênticos
|
| 480 |
+
bit-a-bit entre a op nova com `η ≡ 0` e o scan legado do v0.2.0), `gradiente_xi_eta_vs_fd`
|
| 481 |
+
(cadeia `η_max·tanh(ξ)` → broadcast → empacotamento → scan; erro relativo `1.1e-4`),
|
| 482 |
+
`bloco_ssd_formas_de_estado` (formas do estado e limite `|η| < η_max`), `custo_por_passo` (benchmark).
|
| 483 |
+
|
| 484 |
+
---
|
| 485 |
+
|
| 486 |
+
## 7. Custo extra por passo (`k = 1` vs `k = 2`), medido em processo limpo
|
| 487 |
+
|
| 488 |
+
Método: `cargo test --release --offline --lib ssd::testes::custo_por_passo -- --nocapture` com a variável
|
| 489 |
+
de ambiente `KHTST_BENCH_K` fixando a ordem (1 e 2), em **processos separados** (uma execução por ordem),
|
| 490 |
+
`b = 4`, `n = 1024`, `d = 128`, `30` repetições após aquecimento, CPU 2 vCPU.
|
| 491 |
+
|
| 492 |
+
```
|
| 493 |
+
$ KHTST_BENCH_K=1 cargo test --release --offline --lib ssd::testes::custo_por_passo -- --nocapture
|
| 494 |
+
[custo] k=1 b=4 n=1024 d=128: fwd 0.016 ms (15.83 µs/passo) | fwd+bwd 0.183 ms | por passo (b·d): 0.031 µs
|
| 495 |
+
[custo] k=1 b=4 n=1024 d=128: fwd 0.018 ms (17.59 µs/passo) | fwd+bwd 0.162 ms | por passo (b·d): 0.034 µs
|
| 496 |
+
[custo] k=1 b=4 n=1024 d=128: fwd 0.020 ms (19.84 µs/passo) | fwd+bwd 0.177 ms | por passo (b·d): 0.039 µs
|
| 497 |
+
|
| 498 |
+
$ KHTST_BENCH_K=2 cargo test --release --offline --lib ssd::testes::custo_por_passo -- --nocapture
|
| 499 |
+
[custo] k=2 b=4 n=1024 d=128: fwd 0.026 ms (25.27 µs/passo) | fwd+bwd 0.203 ms | por passo (b·d): 0.049 µs
|
| 500 |
+
[custo] k=2 b=4 n=1024 d=128: fwd 0.027 ms (25.91 µs/passo) | fwd+bwd 0.310 ms | por passo (b·d): 0.051 µs
|
| 501 |
+
[custo] k=2 b=4 n=1024 d=128: fwd 0.013 ms (12.24 µs/passo) | fwd+bwd 0.294 ms | por passo (b·d): 0.024 µs
|
| 502 |
+
```
|
| 503 |
+
|
| 504 |
+
**Resumo (medianas das 3 execuções por ordem):**
|
| 505 |
+
|
| 506 |
+
| | `k = 1` | `k = 2` | razão |
|
| 507 |
+
|---|---|---|---|
|
| 508 |
+
| forward | `0.018 ms` (`17.6 µs/passo`) | `0.026 ms` (`25.3 µs/passo`) | **≈ 1.5×** |
|
| 509 |
+
| forward+backward | `0.177 ms` (`0.034 µs` por passo·(b·d)) | `0.294 ms` (`0.050 µs` por passo·(b·d)) | **≈ 1.6×** |
|
| 510 |
+
|
| 511 |
+
Complexidade: o laço interno da op roda `k` vezes por `(passo, canal)` ⇒ `O(k)` para o scan;
|
| 512 |
+
o backward recomputa os estados (`O(k)`) e faz `k` passes de propagação ⇒ `O(k)`.
|
| 513 |
+
O custo **não** é exatamente `2×` porque há overhead fixo (alocação dos tensores de saída
|
| 514 |
+
`(b,n,k,d)`, do gradiente, do `cat`/`contiguous` do empacotamento e da cópia do estado).
|
| 515 |
+
Medição com ruído: a máquina é compartilhada (2 vCPU, outros agentes compilando no mesmo `CARGO_TARGET_DIR`);
|
| 516 |
+
as execuções acima são **medianas de 3 processos limpos**, e o pior caso observado chegou a `0.030 ms` (fwd, `k=2`).
|
| 517 |
+
|
| 518 |
+
**Memória:** a saída da op passa de `(b,n,d)` para `(b,n,k,d)` (fator `k` na ativação do scan) e o
|
| 519 |
+
backward materializa `O(b·d·k·(n+1))` floats de estados recomputados. Para `k = 2` isso é ~2× a
|
| 520 |
+
ativação do slot — relevante para o orçamento de memória do tronco (o SSD é 1 de 6 camadas).
|
| 521 |
+
|
| 522 |
+
---
|
| 523 |
+
|
| 524 |
+
## 8. Limitações e achados
|
| 525 |
+
|
| 526 |
+
### 8.1 Limitações desta implementação
|
| 527 |
+
|
| 528 |
+
1. **CPU apenas.** A op implementa `cpu_fwd`; `cuda_fwd`/`metal_fwd` não existem (o scan legado do v0.2.0
|
| 529 |
+
também não tinha) — em GPU, `apply_op3` erro. O tronco KHTST_RUST roda em CPU de todo modo.
|
| 530 |
+
2. **Sequencialidade.** O scan é estritamente causal (`t` crescendo) e não paraleliza por passo; o paralelismo
|
| 531 |
+
é por canal/lote (BUG-M7 preservado: uma op para o lote inteiro).
|
| 532 |
+
3. **`η` independente do tempo.** `η^{(k)}_j` é parâmetro por ordem e canal (não função de `x_t` nem de `t`).
|
| 533 |
+
O enunciado escreve `η_t^{(k)}`; a parametrização é a mais simples que preserva "learnable, por canal",
|
| 534 |
+
e o `t` na notação é vacuous nessa leitura. Uma porta *função de `x_t`* (tipo `σ(W_η x_t)`) seria
|
| 535 |
+
uma extensão natural (a op já aceita `η` por canal e por tempo — bastaria passar um tensor `(b,n,d,k)`).
|
| 536 |
+
4. **O `k` é global do bloco.** Não há mixture-of-orders: o slot usa a ordem `k` inteira. O
|
| 537 |
+
`fwd_ordens` expõe todas as ordens para que o agente H possa, por exemplo, projetar a ordem 1 e a ordem 2
|
| 538 |
+
separadamente.
|
| 539 |
+
5. **Forma fechada do Teo S-3 corrigida** (ver §3): a implementação satisfaz o Teo S-3 **corrigido**; a forma
|
| 540 |
+
literal do enunciado corresponde a outra convenção e a discrepância está quantificada e testada.
|
| 541 |
+
6. **Validação por FD no nível do bloco é impossível** pelo motivo de §8.2 (não por defeito do r-13).
|
| 542 |
+
7. **Sem CUDA/GPU, sem `f64`.** Tudo em `f32` (o repo é `f32`); as tolerâncias (`5e-3`) são as do padrão do repo.
|
| 543 |
+
|
| 544 |
+
### 8.2 ⚠ Achado PRÉ-EXISTENTE e grave (fora do escopo do r-13 → reportado ao orquestrador)
|
| 545 |
+
|
| 546 |
+
`src/camadas.rs`:
|
| 547 |
+
|
| 548 |
+
```rust
|
| 549 |
+
impl RmsNorm {
|
| 550 |
+
pub fn fwd(&self, x: &Tensor) -> Result<Tensor> {
|
| 551 |
+
rms_norm(x, self.ganho.as_tensor(), self.eps as f32) // candle_nn::ops::rms_norm
|
| 552 |
+
}
|
| 553 |
+
}
|
| 554 |
+
```
|
| 555 |
+
|
| 556 |
+
No **candle-nn 0.11**, `ops::rms_norm` é:
|
| 557 |
+
|
| 558 |
+
```rust
|
| 559 |
+
pub fn rms_norm(xs: &Tensor, alpha: &Tensor, eps: f32) -> Result<Tensor> {
|
| 560 |
+
...
|
| 561 |
+
xs.apply_op2_no_bwd(alpha, &RmsNorm { eps }) // ← SEM BACKWARD
|
| 562 |
+
}
|
| 563 |
+
```
|
| 564 |
+
|
| 565 |
+
e o `CustomOp2` `RmsNorm` do candle-nn 0.11 **não implementa `bwd`**. Consequência: **o gradiente é
|
| 566 |
+
cortado em toda normalização RMS do tronco** — o gradiente wrt a *entrada* da normalização (e o do seu
|
| 567 |
+
ganho `γ`) é silenciosamente descartado. Verificado experimentalmente: com `L = Σ rms_norm(x)`, o mapa de
|
| 568 |
+
gradientes **não contém** a entrada `x` (`gs.get(&x) == None`).
|
| 569 |
+
|
| 570 |
+
Efeitos:
|
| 571 |
+
|
| 572 |
+
* o caminho `x → norm1 → (porta_a, proj_x) → scan → proj_o → norm2 → FFN` **não propaga gradiente**;
|
| 573 |
+
sobra apenas o caminho residual (`x2 = x + proj_o(y)`);
|
| 574 |
+
* por isso a validação por diferenças finitas do gradiente de `ξ` **no nível do bloco** é impossível
|
| 575 |
+
(o gradiente analítico é "truncado" no `norm2`, o FD não é) — a validação do r-13 foi feita no nível
|
| 576 |
+
da **op** (grafo completo) e da cadeia `ξ → η → scan`;
|
| 577 |
+
* **o `tronco.rs` inteiro treina com gradiente parcial** — isso é um achado de auditoria de primeira ordem,
|
| 578 |
+
independente do r-13, e **não** foi corrigido aqui (o arquivo não é meu).
|
| 579 |
+
|
| 580 |
+
Correção possível (para quem for dono de `src/camadas.rs`): reimplementar o `RmsNorm` com ops
|
| 581 |
+
diferenciáveis do candle, em vez de `candle_nn::ops::rms_norm`:
|
| 582 |
+
|
| 583 |
+
```rust
|
| 584 |
+
let rms = x.sqr()?.sum_keepdim(candle_core::Dim::Minus1)?.affine(1.0 / d as f64, 0.0)?
|
| 585 |
+
.sqrt()?.affine(1.0, self.eps as f64)?;
|
| 586 |
+
x.broadcast_div(&rms)?.broadcast_mul(self.ganho.as_tensor())
|
| 587 |
+
```
|
| 588 |
+
|
| 589 |
+
(tudo isso é `Op` com backward correto no candle 0.11).
|
| 590 |
+
|
| 591 |
+
### 8.3 Notas de integração para o agente H
|
| 592 |
+
|
| 593 |
+
* `BlocoSsd::novo(d, d_ff, dev)` continua funcionando (`k = 1`); para `k = 2` use
|
| 594 |
+
`BlocoSsd::novo_com_ordem(d, d_ff, 2, dev)`.
|
| 595 |
+
* O `tronco.rs` **não precisa ser editado**; apenas a ponte Python que aloca o cache de inferência precisa
|
| 596 |
+
usar `(b, d, k)` quando `k ≥ 2`.
|
| 597 |
+
* `xi_eta` entrou em `BlocoSsd::vars()` ⇒ o otimizador o treat como parâmetro aprendível automaticamente.
|
| 598 |
+
* `eta_max` é campo público (default `1.0`); reduzir o valor limita mais a magnitude do estado;
|
| 599 |
+
`eta_max = 0` degenera no SSD clássico (η ≡ 0) com `k` qualquer.
|
| 600 |
+
|
| 601 |
+
---
|
| 602 |
+
|
| 603 |
+
## 9. Resumo em 5 linhas
|
| 604 |
+
|
| 605 |
+
1. `src/ssd.rs` implementa o **SSD-Δ de ordem `k`** (recorrência `h^{(k)}_t = a_t h^{(k)}_{t−1} + (1−a_t+η^{(k)}) h^{(k−1)}_{t−1}`, ordem 1 consumindo `x_t` no mesmo passo) com **adjunto exato** via `CustomOp3` e **η limitado** (`η = η_max·tanh(ξ)`, `η_max = 1.0`).
|
| 606 |
+
2. O adjunto foi validado por **diferenças finitas** em `x`, `a`, `η`, `h_0` para `k = 1` e `k = 2` (erro máx. `1.0e-3` < `5e-3`) e o `k = 1` é **bit-a-bit idêntico** ao scan legado (90/90 valores).
|
| 607 |
+
3. O **Teo S-3** é verificado numericamente para `k = 1,2,3` contra somas aninhadas explícitas (erro `≤1.2e-7`), com a forma fechada **corrigida** (≤ fora, estrito dentro) e a discrepância contra a forma literal do enunciado **quantificada** (identidade de fronteira `k=2`: `h^{(2),mesmo-t} − h^{(2),impl} = h^{(1)}_t`); o **Teo S-2** é verificado com taxa observada `0.900000` = cota `0.900000`.
|
| 608 |
+
4. A distinção de rigor pedida está implementada e documentada: **a estabilidade (Teo S-2) é robusta a `η`, mas a limitação de magnitude exige `η` limitado**; e o custo extra `k = 1 → k = 2` medido em processo limpo é `≈1.6×` no forward e no forward+backward (`0.018 → 0.026 ms` e `0.177 → 0.294 ms`, `b=4, n=1024, d=128`).
|
| 609 |
+
5. ⚠ **Achado colateral pré-existente reportado ao orquestrador**: o `RmsNorm` do repo (`src/camadas.rs`) usa `candle_nn::ops::rms_norm`, que no candle-nn 0.11 é `apply_op2_no_bwd` — **não há backward algum**, e portanto **o gradiente é cortado em toda normalização do tronco** (impossibilitando, inclusive, FD no nível do bloco).
|
docs/referencias/I_som_variantes.md
ADDED
|
@@ -0,0 +1,622 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AGENTE (I) — SOM de Kohonen e variantes, em Rust — ciclo v0.4.0
|
| 2 |
+
|
| 3 |
+
**Data:** 2026-10-25 (UTC) · **Árvore de trabalho:** `/workspace/khtst_audit/v040`
|
| 4 |
+
**Arquivo entregue:** `src/som.rs` (2 999 linhas) — **arquivo exclusivo deste agente**
|
| 5 |
+
**Regra aplicada:** nenhum número deste relatório vem de estimativa — todos vêm de
|
| 6 |
+
comandos executados, reproduzidos abaixo. Nenhum arquivo fora de `src/som.rs` foi
|
| 7 |
+
editado (o relatório é arquivo novo, exigido como entregável).
|
| 8 |
+
|
| 9 |
+
```
|
| 10 |
+
$ export PATH="$HOME/.cargo/bin:$PATH"
|
| 11 |
+
$ export CARGO_TARGET_DIR=/workspace/khtst_audit/repo_rust/target
|
| 12 |
+
$ cd /workspace/khtst_audit/v040
|
| 13 |
+
$ cargo build --release --offline
|
| 14 |
+
Finished `release` profile [optimized] target(s) in 1m 08s
|
| 15 |
+
$ cargo test --release --offline --lib som:: -- --nocapture
|
| 16 |
+
test result: ok. 8 passed; 0 failed; 1 ignored; 0 measured; 54 filtered out
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
> **Nota sobre a árvore compartilhada.** Durante a sessão, outros agentes editaram
|
| 20 |
+
> `src/config.rs`, `src/atencao.rs`, `src/mistura.rs`, `src/amostrador.rs` etc. A árvore
|
| 21 |
+
> chegou a não compilar (`src/config.rs:176` com `d_false` inexistente, e depois um
|
| 22 |
+
> delimitador aberto em `src/atencao.rs`) — **não é do meu módulo**. No estado atual
|
| 23 |
+
> a árvore compila e os testes do `som` passam; sobram **3 falhas em
|
| 24 |
+
> `src/amostrador.rs`** (`teste_bissecao_tau_converge`, `teste_h_entropia_monotona`,
|
| 25 |
+
> `teste_modo_auto_deriva_alvo_por_nivel`) — arquivo de outro agente, modificado às
|
| 26 |
+
> 13:22 durante a minha sessão, e **nenhum outro módulo referencia `som::`**
|
| 27 |
+
> (`grep -rn "som::" src/*.rs | grep -v som.rs` → vazio), logo são falhas
|
| 28 |
+
> independentes do meu trabalho.
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## 1. API PÚBLICA (assinaturas exatas e formas dos tensores)
|
| 33 |
+
|
| 34 |
+
### 1.1 Ponto de entrada
|
| 35 |
+
|
| 36 |
+
```rust
|
| 37 |
+
pub fn versao() -> &'static str; // "som-0.4.0"
|
| 38 |
+
|
| 39 |
+
pub fn treinar_som(
|
| 40 |
+
dados: &Tensor, // (n, d) f32, CPU — o conjunto de treino
|
| 41 |
+
cfg: &CfgSom, // configuração completa (ver 1.3)
|
| 42 |
+
dev: &Device, // dispositivo dos tensores de saída
|
| 43 |
+
) -> Result<SomTreinado>;
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
### 1.2 Enums de configuração
|
| 47 |
+
|
| 48 |
+
```rust
|
| 49 |
+
pub enum Topologia { Retangular, Hexagonal }
|
| 50 |
+
pub enum FuncaoVizinhanca { Gaussiana, Triangular, Bubble }
|
| 51 |
+
pub enum MetricaDistancia { Euclidiana, Mahalanobis }
|
| 52 |
+
pub enum TaxaAprendizado { Constante(f64), Decaimento { eta0: f64, tau: f64 } }
|
| 53 |
+
pub enum VarianteSom { Basico, Gsom, Parametrico, Kernel }
|
| 54 |
+
pub enum Inicializacao { Pca, Aleatoria }
|
| 55 |
+
pub enum ModoAtualizacao { Lote, Online }
|
| 56 |
+
|
| 57 |
+
pub const OFFSETS_HEX: [(i32, i32); 6]; // vizinhança hexagonal (offsets axiais)
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
`TaxaAprendizado::em(t)` devolve `η_t` (`η₀·e^{−t/τ}`, `t` = índice da época).
|
| 61 |
+
|
| 62 |
+
### 1.3 `CfgSom` (todos os campos são `pub`)
|
| 63 |
+
|
| 64 |
+
| campo | tipo | padrão | papel |
|
| 65 |
+
|---|---|---|---|
|
| 66 |
+
| `variante` | `VarianteSom` | `Basico` | Basico / Gsom / Parametrico / Kernel |
|
| 67 |
+
| `topologia` | `Topologia` | `Retangular` | grade retangular ou hexagonal |
|
| 68 |
+
| `vizinhanca` | `FuncaoVizinhanca` | `Gaussiana` | kernel `h(d)` |
|
| 69 |
+
| `distancia` | `MetricaDistancia` | `Euclidiana` | BMU e EQ |
|
| 70 |
+
| `taxa` | `TaxaAprendizado` | `Constante(0.5)` | lei de `η_t` |
|
| 71 |
+
| `modo` | `ModoAtualizacao` | `Lote` | lote (paralelizável) ou online (Kohonen literal) |
|
| 72 |
+
| `inicializacao` | `Inicializacao` | `Pca` | grade regular em PCA ou uniforme |
|
| 73 |
+
| `largura` / `largura_final` | `f64` | `1.5` / `0.5` | `σ₀` e `σ₁` (decaimento **linear**) |
|
| 74 |
+
| `epochs` | `usize` | `50` | épocas |
|
| 75 |
+
| `lado` / `linhas` | `usize` / `Option<usize>` | `8` / `None` | grade (`None` ⇒ quadrada) |
|
| 76 |
+
| `border_weighting` | `bool` | `true` | σ efetivo reduzido na borda |
|
| 77 |
+
| `eps_cov` | `f64` | `1e-4` | regularização `Σ + εI` |
|
| 78 |
+
| `semente` | `Option<u64>` | `None` | `None` ⇒ herda `crate::rng::semente_atual()` |
|
| 79 |
+
| `paralelo` | `bool` | `true` | rayon vs laço serial puro |
|
| 80 |
+
| `beta` | `f64` | `1.0` | temperatura do softmax de responsabilidades |
|
| 81 |
+
| `limiar_crescimento` | `f64` | `0.0` | GSOM: erro de borda que dispara o crescimento |
|
| 82 |
+
| `lado_max` / `lado_inicial` | `usize` / `Option<usize>` | `16` / `None` | GSOM: teto e grade inicial (`None` ⇒ 2) |
|
| 83 |
+
| `calcular_metricas` | `bool` | `true` | `false` ⇒ só o núcleo de treino (medição de custo) |
|
| 84 |
+
|
| 85 |
+
### 1.4 `SomTreinado` (resultado)
|
| 86 |
+
|
| 87 |
+
```rust
|
| 88 |
+
pub struct SomTreinado {
|
| 89 |
+
pub prototipos: Tensor, // (C, d) C = linhas * lado
|
| 90 |
+
pub lado: usize, // nº de colunas
|
| 91 |
+
pub linhas: usize, // nº de linhas
|
| 92 |
+
pub d: usize,
|
| 93 |
+
pub topologia: Topologia,
|
| 94 |
+
pub variante: VarianteSom,
|
| 95 |
+
pub distancia: MetricaDistancia,
|
| 96 |
+
pub massa: Vec<f64>, // (C,) Σ = 1 (responsabilidade média)
|
| 97 |
+
pub covariancia_celulas: Option<Vec<f32>>, // (C·d·d,) só na variante paramétrica
|
| 98 |
+
pub pesos: Option<Vec<f64>>, // (C,) π_c — variante paramétrica
|
| 99 |
+
pub covariancia_global: Option<Vec<f32>>, // (d·d,) quando Mahalanobis
|
| 100 |
+
pub cholesky_global: Option<Vec<f32>>, // (d·d,) L de Σ+εI
|
| 101 |
+
pub cholesky_celulas: Option<Vec<f32>>, // (C·d·d,) L por célula
|
| 102 |
+
pub codigo_nivel1: Option<Tensor>, // (K, d) codebook anexado
|
| 103 |
+
pub beta: f64,
|
| 104 |
+
pub metricas: MetricasSom, // EQ/TE finais
|
| 105 |
+
pub metricas_iniciais: MetricasSom, // EQ/TE da primeira época
|
| 106 |
+
pub historia: Vec<MetricasSom>, // curva por época
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
pub struct MetricasSom { pub eq: f64, pub te: f64 }
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
### 1.5 Métodos
|
| 113 |
+
|
| 114 |
+
```rust
|
| 115 |
+
impl SomTreinado {
|
| 116 |
+
pub fn n_celulas(&self) -> usize;
|
| 117 |
+
pub fn prototipo(&self, indice: usize) -> Result<Tensor>; // (d,) — erro se indice ≥ C
|
| 118 |
+
pub fn para_tensor(&self) -> Result<Tensor>; // (C, d)
|
| 119 |
+
pub fn massa_por_celula(&self) -> Result<Tensor>; // (C,)
|
| 120 |
+
pub fn responsabilidades(&self, dado: &Tensor) -> Result<Tensor>; // (n, C) — softmax
|
| 121 |
+
pub fn metricas(&self, dados: &Tensor) -> Result<MetricasSom>; // EQ/TE em qualquer conjunto
|
| 122 |
+
pub fn definir_codebook_nivel1(&mut self, cb: &Tensor) -> Result<()>; // cb: (K, d)
|
| 123 |
+
pub fn prior_nivel1(&self, k: usize) -> Result<Tensor>; // (k,)
|
| 124 |
+
pub fn prior_nivel1_com_codebook(&self, cb: &Tensor) -> Result<Tensor>; // (k,)
|
| 125 |
+
pub fn codebook_de_niveis(&self, k: usize) -> Result<Tensor>; // (k, d)
|
| 126 |
+
}
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
**Formas:** `responsabilidades` → `(n, C)` f32, cada linha soma 1; `prior_nivel1` →
|
| 130 |
+
`(k,)` f32, soma 1; `prototipo` → `(d,)`; `para_tensor` → `(C, d)`.
|
| 131 |
+
|
| 132 |
+
---
|
| 133 |
+
|
| 134 |
+
## 2. VARIANTES IMPLEMENTADAS E JUSTIFICATIVA (2 das 3 pedidas + a 3ª)
|
| 135 |
+
|
| 136 |
+
O pedido era: **(1)** GSOM, **(2)** HSOM hierárquico **ou** SOM paramétrico, **(3)** TSOM
|
| 137 |
+
**ou** Kernel SOM. Implementei **quatro** motores com a **mesma API**
|
| 138 |
+
(`CfgSom::variante`):
|
| 139 |
+
|
| 140 |
+
### 2.1 `VarianteSom::Basico` — SOM de Kohonen (núcleo)
|
| 141 |
+
|
| 142 |
+
Grade fixa; BMU por métrica configurada; atualização *batch* (forma normalizada,
|
| 143 |
+
estável) ou *online* (Kohonen literal, serial).
|
| 144 |
+
|
| 145 |
+
### 2.2 `VarianteSom::Gsom` — *Growing SOM* (variante 1 pedida)
|
| 146 |
+
|
| 147 |
+
A grade **cresce** quando a nuvem de dados se expande:
|
| 148 |
+
|
| 149 |
+
* a cada época, mede-se o **erro quadrático máximo por borda** (topo/base/esq/dir)
|
| 150 |
+
entre as amostras cujo BMU está nessa borda;
|
| 151 |
+
* se o erro da borda é **maior que o EQ médio da época** (borda sub-coberta), insere-se
|
| 152 |
+
uma linha/coluna nessa borda;
|
| 153 |
+
* os novos protótipos são a **extrapolação linear** do protótipo da borda para fora:
|
| 154 |
+
`p_novo = p_borda + (p_borda − p_vizinho)`;
|
| 155 |
+
* desempate **equilibrado**: entre as bordas sub-cobertas, prefere o eixo
|
| 156 |
+
(linhas/colunas) cujo tamanho atual da grade é menor — sem isso uma nuvem
|
| 157 |
+
isotrópica só cresceria num eixo (todas as bordas empatam), produzindo grades
|
| 158 |
+
degeneradas (medido: 10×4 antes do desempate, 10×9 depois);
|
| 159 |
+
* `lado_max` trava o crescimento.
|
| 160 |
+
|
| 161 |
+
**Medido** (`teste_gsom_cresce`, 400 amostras uniformes em `[-3,3]²`, grade inicial 2×2):
|
| 162 |
+
|
| 163 |
+
```
|
| 164 |
+
GSOM: células iniciais = 4, finais = 90 (grade 10x9)
|
| 165 |
+
GSOM dimensão 0: dados [-2.95,2.98] | protótipos [-2.64,2.68] | cobertura 89.8%
|
| 166 |
+
GSOM dimensão 1: dados [-3.00,2.98] | protótipos [-2.68,2.69] | cobertura 89.7%
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
### 2.3 `VarianteSom::Parametrico` — SOM paramétrico (a 2ª das 3 pedidas) — **escolhido**
|
| 170 |
+
|
| 171 |
+
Protótipos = **Gaussianas por célula** (média `μ_c` + covariância `Σ_c`), atualizadas
|
| 172 |
+
por **EM local**:
|
| 173 |
+
|
| 174 |
+
* E-step: `r_c(x) ∝ π_c · N(x; μ_c, Σ_c)` (Mahalanobis por célula, Cholesky explícito);
|
| 175 |
+
* **prior topológico**: a vizinhança `h(c, bmu(x))` (kernel da grade) pondera as
|
| 176 |
+
responsabilidades — as células vizinhas do BMU absorvem parte da massa (é o
|
| 177 |
+
"local EM", equivalente ao papel que a vizinhança tem no SOM clássico);
|
| 178 |
+
* M-step: `π_c`, `μ_c` e `Σ_c` (com `+εI`), com taxa `η_t`.
|
| 179 |
+
|
| 180 |
+
**Por que o paramétrico e não o HSOM hierárquico.** O problema que motiva este
|
| 181 |
+
agente é o **prior degenerado do nível 1** (`p(c₁)` incondicional). Um HSOM
|
| 182 |
+
hierárquico (SOM grosseiro + SOM fino por célula) resolve a * granularidade
|
| 183 |
+
espacial*, mas **não produz uma densidade sobre o espaço**: no nível 1 ele
|
| 184 |
+
degenera em "um código grosseiro por célula", e a distribuição sobre os K códigos
|
| 185 |
+
continua sendo (no melhor caso) uma **contagem** de massa por região — que é
|
| 186 |
+
degenerada se o codebook não forbalanceado, e não é uma distribuição *derivada de
|
| 187 |
+
uma densidade*. Já o SOM paramétrico **é** uma mistura de Gaussianas sobre o espaço
|
| 188 |
+
de conceitos: `p(c₁=k)` pode ser lido como `∫ N(x; μ_k, Σ_k) q(x) dx` com `q` a
|
| 189 |
+
densidade aprendida pelo SOM — uma distribuição de suporte completo por construção.
|
| 190 |
+
Bônus: o paramétrico **dá de graça** a métrica de Mahalanobis pedida no ciclo
|
| 191 |
+
(cada célula tem a sua covariância ⇒ o Cholesky por célula *é* a Mahalanobis
|
| 192 |
+
local), e é a variante que melhor se conecta ao `prior_nivel1` (as massas `m_j`
|
| 193 |
+
saem das responsabilidades da mistura). O HSOM fica como trabalho futuro (ver §6).
|
| 194 |
+
|
| 195 |
+
### 2.4 `VarianteSom::Kernel` — Kernel SOM (a 3ª pedida)
|
| 196 |
+
|
| 197 |
+
O peso vem do **espaço de features**, não da grade: `K(x, p_c) = exp(−‖x−p_c‖²/(2σ²))`
|
| 198 |
+
e o protótipo é a média ponderada (forma fechada):
|
| 199 |
+
|
| 200 |
+
```
|
| 201 |
+
p_c ← Σ_x K(x, p_c)·x / Σ_x K(x, p_c)
|
| 202 |
+
```
|
| 203 |
+
|
| 204 |
+
Diferença em relação ao `Basico`: no `Basico` o peso é `h(d_grid(c, bmu(x)))` (a
|
| 205 |
+
vizinhança étopológica); no `Kernel` o peso é `K(x, p_c)` (a similaridade é no
|
| 206 |
+
espaço de features e a topologia só ordena/initializa os protótipos). O TSOM foi
|
| 207 |
+
descartado porque, como o próprio enunciado observa, "taxa e largura dependentes do
|
| 208 |
+
tempo já estão cobertas pelo decaimento" (`TaxaAprendizado::Decaimento` + o
|
| 209 |
+
decaimento linear de `σ`).
|
| 210 |
+
|
| 211 |
+
---
|
| 212 |
+
|
| 213 |
+
## 3. `prior_nivel1`: PROVA DE QUE É UMA DISTRIBUIÇÃO VÁLIDA E NÃO-DEGENERADA
|
| 214 |
+
|
| 215 |
+
### 3.1 A decisão de mapeamento (SOM → codebook do nível 1)
|
| 216 |
+
|
| 217 |
+
Cada código `c_i` do codebook do nível 1 (matriz `(K, d)`, `d = d_lat`) é associado
|
| 218 |
+
às células do SOM por **responsabilidade softmax sobre as distâncias**:
|
| 219 |
+
|
| 220 |
+
```
|
| 221 |
+
q_ij = softmax_j( −d(c_i, p_j) / τ ), τ = 1/β , d = métrica configurada
|
| 222 |
+
p(c₁ = i) = Σ_j m_j · q_ij
|
| 223 |
+
```
|
| 224 |
+
|
| 225 |
+
onde `m_j` é a **massa** da célula `j` (responsabilidade média sobre o conjunto de
|
| 226 |
+
treino, `Σ_j m_j = 1`) e `p_j` é o protótipo da célula.
|
| 227 |
+
|
| 228 |
+
**Por que esta escolha e não "distância do protótipo ao codebook" (argmax duro) nem
|
| 229 |
+
"responsabilidade somada por código".**
|
| 230 |
+
|
| 231 |
+
* **Argmax duro** (`a(i) = argmin_j d(c_i, p_j)`, `p_i = m_{a(i)}`) **não é uma
|
| 232 |
+
distribuição**: (i) vários códigos podem cair na mesma célula (o prior passa a
|
| 233 |
+
somar > 1 ou < 1 conforme a multiplicidade); (ii) códigos sem célula próxima
|
| 234 |
+
ficam com massa 0 (suporte não completo ⇒ degenerate). Com a forma
|
| 235 |
+
"responsabilidade" o problema (i) desaparece por construção (as linhas de `q`
|
| 236 |
+
somam 1) e o (ii) também (o softmax é estritamente positivo).
|
| 237 |
+
* **"Responsabilidade somada por código"** (aplicar o softmax do codebook
|
| 238 |
+
diretamente aos dados, `p_i = Σ_x r_i(x)`) é uma média do codebook sobre os
|
| 239 |
+
dados **não se apoia nos protótipos**: ignora o que o SOM aprendeu (a topologia e a
|
| 240 |
+
densidade local) e devolve algo próximo do próprio codebook — não é o "SOM como
|
| 241 |
+
prior". A forma escolhida **propaga a densidade aprendida pelo SOM** (via `m_j`)
|
| 242 |
+
**e** reconcilia com o codebook (via `q_ij`): `p` é a lei da total
|
| 243 |
+
probabilidade `P(código = i) = Σ_j P(célula = j)·P(código = i | célula = j)`.
|
| 244 |
+
|
| 245 |
+
### 3.2 Prova de validade
|
| 246 |
+
|
| 247 |
+
`q` é linha-estocástica por construção (softmax): `Σ_j q_ij = 1` e `q_ij ≥ 0`.
|
| 248 |
+
`m` é uma distribuição por construção (`m_j ≥ 0`, `Σ_j m_j = 1` — a massa é a média
|
| 249 |
+
das responsabilidades softmax, que somam 1 por amostra).
|
| 250 |
+
|
| 251 |
+
Logo, para todo `i`:
|
| 252 |
+
|
| 253 |
+
```
|
| 254 |
+
p_i = Σ_j m_j q_ij ≥ 0 (ambos os fatores ≥ 0)
|
| 255 |
+
Σ_i p_i = Σ_i Σ_j m_j q_ij = Σ_j m_j (Σ_i q_ij) = Σ_j m_j · 1 = 1
|
| 256 |
+
```
|
| 257 |
+
|
| 258 |
+
⇒ `p` é uma **distribuição válida** sobre os K códigos. (O código ainda renormaliza
|
| 259 |
+
`p` por `Σ p` — defesas contra erro de ponto flutuante — o que não muda a
|
| 260 |
+
propriedade.) *Teste*: `assert!((soma - 1.0).abs() < 1e-5)` e
|
| 261 |
+
`assert!(v >= 0.0)` para todo `i`.
|
| 262 |
+
|
| 263 |
+
### 3.3 Prova de não-degeneração
|
| 264 |
+
|
| 265 |
+
Com `τ` finito e `β > 0`, o softmax é **estritamente positivo**: `q_ij > 0` para todo
|
| 266 |
+
`(i, j)`. Suponha que `p` fosse degenerado, isto é, `p_{i₀} = 1` (e `p_i = 0` para
|
| 267 |
+
`i ≠ i₀`). Então
|
| 268 |
+
|
| 269 |
+
```
|
| 270 |
+
1 = p_{i₀} = Σ_j m_j q_{i₀ j} ≤ Σ_j m_j · 1 = 1
|
| 271 |
+
```
|
| 272 |
+
|
| 273 |
+
e a igualdade exige `q_{i₀ j} = 1` para **todo** `j` com `m_j > 0`. Mas `q_{i₀ j} = 1`
|
| 274 |
+
com `q_{i₀ j'} > 0` para `j' ≠ j` é impossível (a linha do softmax soma 1 e todos os
|
| 275 |
+
seus termos são estritamente positivos). Logo, para `C ≥ 2` e pelo menos duas células
|
| 276 |
+
com massa positiva, **`p_{i₀} < 1` para todo `i₀`**, e portanto `p` **não** é uma
|
| 277 |
+
massa pontual: `H(p) = −Σ p log p > 0`.
|
| 278 |
+
|
| 279 |
+
Mais forte ainda: `p_i ≥ m_j q_ij > 0` para qualquer `j` com `m_j > 0` ⇒ `p` tem
|
| 280 |
+
**suporte completo** (nenhum código tem probabilidade zero) sempre que o SOM
|
| 281 |
+
distribui massa por ≥ 1 célula e `β` é finito — isto é, **não-degenerado por
|
| 282 |
+
construção**, não por sorte da distribuição de dados.
|
| 283 |
+
|
| 284 |
+
*Teste* (`teste_prior_nivel1_valida`): K = 8 códigos, `d_lat = 4`, SOM 5×5 sobre 500
|
| 285 |
+
amostras:
|
| 286 |
+
|
| 287 |
+
```
|
| 288 |
+
prior = [0.11797946, 0.12132247, 0.13894127, 0.1302716, 0.12559211, 0.119279526, 0.11364038, 0.1329732]
|
| 289 |
+
entropia do prior = 2.0774 nats (máximo teórico = 2.0794)
|
| 290 |
+
```
|
| 291 |
+
|
| 292 |
+
`soma = 1` (|soma−1| < 1e-5), todos os 8 componentes `> 0` (suporte completo),
|
| 293 |
+
`H = 2.0774 nats` contra o máximo teórico `ln 8 = 2.0794 nats` ⇒ o prior é
|
| 294 |
+
praticamente **uniforme** — o oposto do `p(c₁)` degenerado que motivou este agente.
|
| 295 |
+
|
| 296 |
+
### 3.4 Modo "sem codebook anexado"
|
| 297 |
+
|
| 298 |
+
Se `som.codigo_nivel1` é `None`, `prior_nivel1(k)` devolve a massa das `k` células
|
| 299 |
+
mais pobladas (renormalizada) — o "modo identidade", útil quando o level-1 codebook
|
| 300 |
+
é construído a partir do próprio SOM (`codebook_de_niveis`). `k > C` ⇒ erro
|
| 301 |
+
explícito. Testado.
|
| 302 |
+
|
| 303 |
+
---
|
| 304 |
+
|
| 305 |
+
## 4. O PRIOR COMO FUNÇÃO DO CONJUNTO DE TREINO ⇒ FATORAÇÃO PRESERVADA
|
| 306 |
+
|
| 307 |
+
O gerador do KHTST fatoriza
|
| 308 |
+
|
| 309 |
+
```
|
| 310 |
+
p(c_1, …, c_L) = p(c_1) · Π_{ℓ≥2} p(c_ℓ | c_{<ℓ})
|
| 311 |
+
```
|
| 312 |
+
|
| 313 |
+
e o defeito auditado era que `p(c₁)` era **incondicional e degenerado** (uma massa
|
| 314 |
+
de Dirac sobre um código fixo, ou seja, o nível 1 não explorava o codebook).
|
| 315 |
+
|
| 316 |
+
O prior implementado satisfaz as duas propriedades que a fatoração exige:
|
| 317 |
+
|
| 318 |
+
1. **Independência da amostra.** `prior_nivel1(k)` é uma função de
|
| 319 |
+
`(SOM treinado, codebook do nível 1)` — e o SOM treinado é função de
|
| 320 |
+
`(conjunto de treino D, CfgSom)`. Em nenhum ponto entra a amostra que está
|
| 321 |
+
sendo gerada. Consequência: `p(c₁)` é **constante ao longo da geração** (é o
|
| 322 |
+
mesmo vetor para todas as amostras do lote), logo
|
| 323 |
+
|
| 324 |
+
```
|
| 325 |
+
p(c_1, …, c_L) = prior · Π_{ℓ≥2} p(c_ℓ | c_{<ℓ})
|
| 326 |
+
```
|
| 327 |
+
|
| 328 |
+
continua sendo uma fatoração válida: o prior é apenas o **marginal do primeiro
|
| 329 |
+
fator**, e os fatores condicionais `p(c_ℓ | c_{<ℓ})` (do gerador/verificador)
|
| 330 |
+
permanecem inalterados. Trocar `p(c₁)` degenerado por este prior **não**
|
| 331 |
+
reintroduz vazamento entre níveis: o prior não transporta informação da
|
| 332 |
+
amostra atual para os níveis seguintes — ele é uma **propriedade do modelo
|
| 333 |
+
treinado**, não do contexto.
|
| 334 |
+
|
| 335 |
+
2. **Não-degeneração (não-Dirac).** Como provado em §3, o prior tem suporte
|
| 336 |
+
completo e entropia > 0, de modo que o nível 1 passa a ter uma
|
| 337 |
+
**distribuição real** sobre os K códigos (e não uma delta). Isso é exatamente o
|
| 338 |
+
que a fatoração exige do primeiro fator: `p(c₁)` deve ser uma distribuição
|
| 339 |
+
(não um ponto), e os demais fatores condicionam sobre o *valor* de `c₁`.
|
| 340 |
+
|
| 341 |
+
**Mapeamento concreto para a integração (o que o integrador deve fazer):**
|
| 342 |
+
|
| 343 |
+
```rust
|
| 344 |
+
// 1) treinar o SOM sobre os vetores de conceito do nível 1 (d_lat)
|
| 345 |
+
let cfg = CfgSom { lado: 10, epochs: 200, /* … */ };
|
| 346 |
+
let som = treinar_som(&dados_conceito_nivel1, &cfg, &dev)?; // (n, d_lat)
|
| 347 |
+
|
| 348 |
+
// 2) anexar o codebook do nível 1 (K, d_lat) — o MESMO espaço dos protótipos
|
| 349 |
+
som.definir_codebook_nivel1(&codebook_nivel1)?; // (K, d_lat)
|
| 350 |
+
|
| 351 |
+
// 3) prior sobre os K códigos → usar como p(c_1) na amostragem
|
| 352 |
+
let p1 = som.prior_nivel1(K)?; // (K,), soma 1, H > 0
|
| 353 |
+
```
|
| 354 |
+
|
| 355 |
+
Alternativa (o codebook do nível 1 **derivado** do SOM):
|
| 356 |
+
`som.codebook_de_niveis(K)?` devolve `(K, d_lat)` com os protótipos das `K`
|
| 357 |
+
células mais pobladas; nesse caso `prior_nivel1(K)` cai (aproximadamente) na massa
|
| 358 |
+
dessas células.
|
| 359 |
+
|
| 360 |
+
**Pré-condição honesta:** o mapeamento exige que o codebook do nível 1 viva no
|
| 361 |
+
**mesmo espaço** (`d_lat`) dos protótipos do SOM; caso contrário
|
| 362 |
+
`prior_nivel1` devolve `Err` (dimensão incompatível) em vez de um número
|
| 363 |
+
silenciosamente errado.
|
| 364 |
+
|
| 365 |
+
---
|
| 366 |
+
|
| 367 |
+
## 5. MÉTRICAS MEDIDAS (EQ/TE) E GANHO DO RAYON
|
| 368 |
+
|
| 369 |
+
### 5.1 EQ e TE — experimento com estrutura conhecida
|
| 370 |
+
|
| 371 |
+
**Dado sintético ("duas grades"):** dois retângulos densos 8×8 (espaçamento 1)
|
| 372 |
+
lado a lado ⇒ uma superfície 2D contínua 16×7, 128 amostras em `d = 2`. A estrutura
|
| 373 |
+
topológica é *conhecida*: um mapa organizado deve ter TE ≈ 0.
|
| 374 |
+
|
| 375 |
+
**Configuração:** grade 10×10, `σ: 1.5 → 0.5` (linear), `η = 0.5` constante,
|
| 376 |
+
300 épocas, **inicialização aleatória** (o mapa começa *desalinhado* dos dados —
|
| 377 |
+
é o regime em que a auto-organização é visível), semente 42.
|
| 378 |
+
|
| 379 |
+
```
|
| 380 |
+
$ cargo test --release --offline --lib som::testes::teste_som_equilibrio_te -- --nocapture
|
| 381 |
+
=== EQ/TE: duas grades (128 amostras, grade 10x10) ===
|
| 382 |
+
TE: inicial = 0.9453 -> final = 0.0469 (EQ: 0.5735 -> 0.2038)
|
| 383 |
+
época 0: EQ = 0.57349 TE = 0.9453
|
| 384 |
+
época 10: EQ = 0.82553 TE = 0.0391
|
| 385 |
+
época 20: EQ = 0.74594 TE = 0.0469
|
| 386 |
+
época 30: EQ = 0.72645 TE = 0.0312
|
| 387 |
+
época 40: EQ = 0.70740 TE = 0.0312
|
| 388 |
+
época 50: EQ = 0.69307 TE = 0.0312
|
| 389 |
+
época 60: EQ = 0.67707 TE = 0.0234
|
| 390 |
+
época 70: EQ = 0.65493 TE = 0.0234
|
| 391 |
+
época 80: EQ = 0.63648 TE = 0.0156
|
| 392 |
+
época 90: EQ = 0.61331 TE = 0.0156
|
| 393 |
+
época 100: EQ = 0.57192 TE = 0.0156
|
| 394 |
+
época 110: EQ = 0.54333 TE = 0.0234
|
| 395 |
+
época 120: EQ = 0.51649 TE = 0.0234
|
| 396 |
+
época 130: EQ = 0.49910 TE = 0.0156
|
| 397 |
+
época 140: EQ = 0.49327 TE = 0.0156
|
| 398 |
+
época 150: EQ = 0.46978 TE = 0.0234
|
| 399 |
+
época 160: EQ = 0.45181 TE = 0.0156
|
| 400 |
+
época 170: EQ = 0.42228 TE = 0.0156
|
| 401 |
+
época 180: EQ = 0.40221 TE = 0.0234
|
| 402 |
+
época 190: EQ = 0.38171 TE = 0.0156
|
| 403 |
+
época 200: EQ = 0.36486 TE = 0.0234
|
| 404 |
+
época 210: EQ = 0.34912 TE = 0.0234
|
| 405 |
+
época 220: EQ = 0.33188 TE = 0.0234
|
| 406 |
+
época 230: EQ = 0.31683 TE = 0.0234
|
| 407 |
+
época 240: EQ = 0.30226 TE = 0.0234
|
| 408 |
+
época 250: EQ = 0.28794 TE = 0.0234
|
| 409 |
+
época 260: EQ = 0.27367 TE = 0.0234
|
| 410 |
+
época 270: EQ = 0.24927 TE = 0.0312
|
| 411 |
+
época 280: EQ = 0.23328 TE = 0.0391
|
| 412 |
+
época 290: EQ = 0.21807 TE = 0.0391
|
| 413 |
+
época 299: EQ = 0.20383 TE = 0.0469
|
| 414 |
+
PCA: TE inicial = 0.0000 -> final = 0.0156 | EQ 0.3004 -> 0.2058
|
| 415 |
+
```
|
| 416 |
+
|
| 417 |
+
**Leitura:**
|
| 418 |
+
|
| 419 |
+
| | TE inicial | TE final | EQ inicial | EQ final |
|
| 420 |
+
|---|---|---|---|---|
|
| 421 |
+
| mapa **desalinhado** (init. aleatória) | **0.9453** | **0.0469** | **0.5735** | **0.2038** |
|
| 422 |
+
| mapa **já ordenado** (init. PCA) | 0.0000 | 0.0156 | 0.3004 | 0.2058 |
|
| 423 |
+
|
| 424 |
+
* O **TE cai de 0.9453 para 0.0469** (≈ 0) com tempo suficiente ⇒ a estrutura
|
| 425 |
+
topológica conhecida foi preservada.
|
| 426 |
+
* O **EQ cai de 0.5735 para 0.2038** (−64 %).
|
| 427 |
+
* O EQ *sobe* nas primeiras épocas (0.57 → 0.83 na época 10) e só depois cai: é o
|
| 428 |
+
comportamento clássico do SOM — primeiro a grade se **organiza** (a smoothing
|
| 429 |
+
inicial estica os protótipos e a QE sobe), depois **quantiza** (a QE cai). A curva
|
| 430 |
+
completa está acima.
|
| 431 |
+
* Com inicialização PCA o TE já nasce em 0.0000 (o mapa já é topologicamente
|
| 432 |
+
consistente) — a queda de 0.9453 → 0.0469 acima é, portanto, **auto-organização
|
| 433 |
+
observada**, não um artefato da inicialização.
|
| 434 |
+
|
| 435 |
+
### 5.2 Mahalanobis — experimento controlado
|
| 436 |
+
|
| 437 |
+
Crista correlacionada (ρ ≈ 0.95, 600 amostras, `d = 2`), grade 5×5, mesma semente;
|
| 438 |
+
avaliação **na métrica de Mahalanobis** (a "natural" para esse dado):
|
| 439 |
+
|
| 440 |
+
```
|
| 441 |
+
$ cargo test --release --offline --lib som::testes::teste_som_mahalanobis -- --nocapture
|
| 442 |
+
crista correlacionada: EQ_M(SOM euclidiano) = 0.34039 | EQ_M(SOM Mahalanobis) = 0.17312
|
| 443 |
+
```
|
| 444 |
+
|
| 445 |
+
O SOM treinado com Mahalanobis reduz o erro **na própria métrica em ~2×** e produz
|
| 446 |
+
um mapa diferente (os protótipos não coincidem — verificado por `assert_ne!`).
|
| 447 |
+
O teste também confere a corretude numérica: `L·Lᵀ = Σ+εI` e a forma quadrática
|
| 448 |
+
`‖x−y‖²_{Σ+εI}` bate com `Σ⁻¹` calculada por Gauss-Jordan independente.
|
| 449 |
+
|
| 450 |
+
### 5.3 Ganho do rayon (wall-clock, processo limpo)
|
| 451 |
+
|
| 452 |
+
Benchmark embutido em `src/som.rs` (`#[ignore]`, roda sob demanda), **N = 20 000
|
| 453 |
+
amostras, C = 64 células (grade 8×8), d = 16**, mínimo de 5 repetições, processo
|
| 454 |
+
limpo (`cargo test` = processo novo):
|
| 455 |
+
|
| 456 |
+
```
|
| 457 |
+
$ cargo test --release --offline --lib bench_som -- --ignored --nocapture
|
| 458 |
+
--- benchmark SOM (N=20000, C=64, d=16, rayon threads=1, reps=5, mínimo) ---
|
| 459 |
+
SomConceitos (memoria.rs, serial, 1 passada) : 147.458715ms
|
| 460 |
+
SOM serial (1 época, núcleo) : 191.287761ms
|
| 461 |
+
SOM rayon (1 época, núcleo) : 206.069266ms
|
| 462 |
+
SOM rayon (5 épocas, com EQ/TE/massa) : 1.27435013s
|
| 463 |
+
ganho rayon vs serial (1 época) : 0.93x
|
| 464 |
+
ganho rayon vs SomConceitos (1 passada) : 0.72x
|
| 465 |
+
|
| 466 |
+
$ RAYON_NUM_THREADS=2 cargo test --release --offline --lib bench_som -- --ignored --nocapture
|
| 467 |
+
--- benchmark SOM (N=20000, C=64, d=16, rayon threads=2, reps=5, mínimo) ---
|
| 468 |
+
SomConceitos (memoria.rs, serial, 1 passada) : 185.93123ms
|
| 469 |
+
SOM serial (1 época, núcleo) : 223.879257ms
|
| 470 |
+
SOM rayon (1 época, núcleo) : 174.474027ms
|
| 471 |
+
SOM rayon (5 épocas, com EQ/TE/massa) : 657.431285ms
|
| 472 |
+
ganho rayon vs serial (1 época) : 1.28x
|
| 473 |
+
ganho rayon vs SomConceitos (1 passada) : 1.07x
|
| 474 |
+
|
| 475 |
+
$ RAYON_NUM_THREADS=2 cargo test … (repetição)
|
| 476 |
+
SomConceitos (memoria.rs, serial, 1 passada) : 87.543708ms
|
| 477 |
+
SOM serial (1 época, núcleo) : 194.3763ms
|
| 478 |
+
SOM rayon (1 época, núcleo) : 146.701939ms
|
| 479 |
+
ganho rayon vs serial (1 época) : 1.32x
|
| 480 |
+
```
|
| 481 |
+
|
| 482 |
+
**Leitura honesta:**
|
| 483 |
+
|
| 484 |
+
* Com **2 threads** o caminho rayon é **1.28×–1.32× mais rápido** que o mesmo
|
| 485 |
+
algoritmo serial (repetições independentes: 1.28× e 1.32×).
|
| 486 |
+
* Com **1 thread** (o padrão do sandbox) o rayon é ≈ o serial (0.93×–1.08×): sem
|
| 487 |
+
paralelismo há só o *overhead* do pool.
|
| 488 |
+
* O motor novo (núcleo de treino, 1 época) é **da mesma ordem** do laço puro de
|
| 489 |
+
`memoria.rs` (0.72×–1.07×): o `SomConceitos` faz 2 passagens `N·C·d` (BMU +
|
| 490 |
+
atualização), o motor novo faz as mesmas 2 passagens **mais** a massa/responsabilidades
|
| 491 |
+
e o EQ/TE final (que são desligáveis por `CfgSom::calcular_metricas = false`, e é
|
| 492 |
+
assim que o benchmark isola o núcleo).
|
| 493 |
+
* **Por que o ganho é modesto:** o sandbox tem **2 vCPUs visíveis mas quota de
|
| 494 |
+
cgroup de 1.3 CPU** (`/sys/fs/cgroup/cpu.max` = `130000 100000`) e *load average*
|
| 495 |
+
≈ 3.6 (outros agentes compilando em paralelo). O `rayon` ainda assim reporta
|
| 496 |
+
**1 thread** por padrão, porque `std::thread::available_parallelism()` devolve
|
| 497 |
+
`Ok(1)` nesse ambiente — daí a necessidade de `RAYON_NUM_THREADS=2` para medir
|
| 498 |
+
qualquer paralelismo. **Não há como demonstrar o ganho do rayon em escala aqui**;
|
| 499 |
+
o que é verificável e verificei é a **equivalência bit-a-bit** entre a via serial
|
| 500 |
+
e a via rayon (§5.4), que é o requisito mais forte do ciclo (reprodutibilidade).
|
| 501 |
+
* Nota de ruído: as medições variam bastante entre execuções (o `SomConceitos`
|
| 502 |
+
oscilou entre 87 ms e 186 ms em execuções diferentes) — por isso o benchmark
|
| 503 |
+
reporta o **mínimo de 5 repetições** e não a média.
|
| 504 |
+
|
| 505 |
+
### 5.4 Determinismo (requisito do ciclo)
|
| 506 |
+
|
| 507 |
+
```
|
| 508 |
+
$ cargo test --release --offline --lib som::testes::teste_som_deterministico
|
| 509 |
+
test result: ok
|
| 510 |
+
```
|
| 511 |
+
|
| 512 |
+
Verificado em `teste_som_deterministico`:
|
| 513 |
+
|
| 514 |
+
* mesmo dado + mesma semente ⇒ protótipos **bit-a-bit idênticos**
|
| 515 |
+
(comparação por `to_bits()` de cada `f32`), e EQ/TE idênticos;
|
| 516 |
+
* via **serial ≡ via rayon**, bit-a-bit (inclusive com N = 2 500 > `BLOCO` = 1024,
|
| 517 |
+
que exercita o caminho multi-bloco da acumulação);
|
| 518 |
+
* semente diferente ⇒ protótipos diferentes (o SOM não é degenerado).
|
| 519 |
+
|
| 520 |
+
---
|
| 521 |
+
|
| 522 |
+
## 6. LIMITAÇÕES HONESTAS
|
| 523 |
+
|
| 524 |
+
1. **CPU-only.** O laço interno é `Vec<f32>`/`Vec<f64>` puro; a accelerated path do
|
| 525 |
+
candle não é usada. `treinar_som` lê o tensor de entrada via `to_vec1`, que exige
|
| 526 |
+
CPU — tensors CUDA dariam erro. Um port GPU exigiria reescrever o kernel.
|
| 527 |
+
2. **Ambiente sem paralelismo útil.** Com a quota de 1.3 CPU do sandbox, o ganho do
|
| 528 |
+
rayon medido é 1.28×–1.32× com 2 threads e ≈ 1× com 1 thread. O ganho real
|
| 529 |
+
depende da máquina de destino; o que é garantido aqui é a **equivalência
|
| 530 |
+
bit-a-bit** serial ↔ rayon.
|
| 531 |
+
3. **TE e a vizinhança de Moore.** O TE usa a vizinhança de **Moore** (8 células) na
|
| 532 |
+
topologia retangular. Com a vizinhança de Von Neumann (4 células) o TE de um mapa
|
| 533 |
+
2D é trivialmente ≈ 1 (o 2º melhor de uma amostra é quase sempre uma diagonal do
|
| 534 |
+
BMU) e o indicador perde informação — comportamento confirmado empiricamente
|
| 535 |
+
durante o desenvolvimento (TE = 1.0000 em todos os casos com 4-vizinhos). A
|
| 536 |
+
convenção de Moore é a das implementações de referência (`minisom`, `somperf`,
|
| 537 |
+
pacote R `kohonen`, que usam limiar 1.42 ≈ √2 na topologia retangular).
|
| 538 |
+
4. **Atualização em lote ≠ Kohonen online.** O modo `Lote` (padrão, paralelizável)
|
| 539 |
+
é o *batch SOM* (alvo = média ponderada pelo kernel; taxa efetiva `η ≤ 1`, sem
|
| 540 |
+
overshoot). O modo `Online` implementa a regra literal amostra-a-amostra, mas é
|
| 541 |
+
serial por natureza (a ordem das amostras muda o resultado). O `SomConceitos`
|
| 542 |
+
existente é online.
|
| 543 |
+
5. **O GSOM cresce no máximo 1 linha/coluna por época** e o desempate entre eixos é
|
| 544 |
+
heurístico (o eixo com a grade menor). Não é o GSOM "oficial" (Liu et al. 2001),
|
| 545 |
+
que usa um limiar de erro decrescente no tempo; a consequência prática é que o
|
| 546 |
+
GSOM aqui para cedo em nuvens com caudas pesadas (a cobertura medida foi 89.8 %
|
| 547 |
+
do intervalo dos dados).
|
| 548 |
+
6. **HSOM hierárquico não implementado** (justificativa em §2.3): o prior do nível 1
|
| 549 |
+
precisa de uma *densidade*, que o HSOM não fornece. Ele continua sendo a
|
| 550 |
+
extension natural se o nível 1 passar a exigir estrutura hierárquica.
|
| 551 |
+
7. **O prior pressupõe o mesmo espaço.** `prior_nivel1` exige que o codebook do
|
| 552 |
+
nível 1 e os protótipos do SOM vivam no mesmo `d_lat`; a API devolve `Err` em vez
|
| 553 |
+
de silenciosamente misturar espaços.
|
| 554 |
+
8. **A massa `m_j` é uma estimativa** (responsabilidades médias sobre o conjunto de
|
| 555 |
+
treino): com poucas amostras por célula a massa é ruidosa. O prior é não-degenerado
|
| 556 |
+
por construção, mas a *forma* dele reflete a densidade aprendida — com dados
|
| 557 |
+
enviesados, o prior é enviesado (é um prior, não uma verdade).
|
| 558 |
+
9. **Ruído do benchmark.** As medições de wall-clock neste sandbox são ruidosas
|
| 559 |
+
(load ≈ 3.6 por outros agentes); reporto o mínimo de 5 repetições e o ambiente
|
| 560 |
+
(quota de cgroup) para que o número seja interpretável.
|
| 561 |
+
10. **Convivência com a árvore compartilhada.** As 3 falhas restantes do
|
| 562 |
+
`cargo test --lib` são em `src/amostrador.rs` (arquivo de outro agente,
|
| 563 |
+
modificado às 13:22) e são **independentes** deste módulo (nenhum outro
|
| 564 |
+
arquivo referencia `som::`).
|
| 565 |
+
|
| 566 |
+
---
|
| 567 |
+
|
| 568 |
+
## 7. COMO O `SomConceitos` SERIA SUBSTITUÍDO
|
| 569 |
+
|
| 570 |
+
`src/memoria.rs` **não foi tocado** (o módulo novo é *adicional*). Mapa de
|
| 571 |
+
substituição:
|
| 572 |
+
|
| 573 |
+
| capacidade | `SomConceitos` (memoria.rs) | `som.rs` (este módulo) |
|
| 574 |
+
|---|---|---|
|
| 575 |
+
| grade | fixa `lado×lado` | retangular **ou** hexagonal; GSOM cresce |
|
| 576 |
+
| vizinhança | gaussiana σ=1 implícito | gaussiana / triangular / bubble |
|
| 577 |
+
| distância | euclidiana | euclidiana / **Mahalanobis** (Cholesky explícito) |
|
| 578 |
+
| taxa | constante 0.05 | constante / decaimento `η₀e^{−t/τ}` |
|
| 579 |
+
| borda | truncada | truncada **ou** *border weighting* (σ efetivo `min(σ, max(1,d_borda))`) |
|
| 580 |
+
| atualização | online serial | lote (paralelo, determinístico) **ou** online |
|
| 581 |
+
| métricas | `eq_medio` | **EQ** + **TE** + curva por época + massa |
|
| 582 |
+
| paralelismo | nenhum | rayon (determinístico) |
|
| 583 |
+
| uso no gerador | nenhum | `prior_nivel1` (prior não-degenerado) |
|
| 584 |
+
|
| 585 |
+
Migração mecânica:
|
| 586 |
+
|
| 587 |
+
```rust
|
| 588 |
+
// antes
|
| 589 |
+
let mut sc = SomConceitos::novo(lado, d_lat, &dev)?;
|
| 590 |
+
sc.inserir(&z_conceitos)?; // (n, d_lat)
|
| 591 |
+
// sc.eq_medio
|
| 592 |
+
|
| 593 |
+
// depois
|
| 594 |
+
let cfg = CfgSom { lado, epochs: 200, /* … */ semente: Some(42), ..CfgSom::default() };
|
| 595 |
+
let som = treinar_som(&z_conceitos, &cfg, &dev)?;
|
| 596 |
+
som.definir_codebook_nivel1(&codebook_nivel1)?; // (K, d_lat)
|
| 597 |
+
let p1 = som.prior_nivel1(K)?; // (K,) — p(c_1) não-degenerado
|
| 598 |
+
```
|
| 599 |
+
|
| 600 |
+
---
|
| 601 |
+
|
| 602 |
+
## 8. RESUMO EM 5 LINHAS
|
| 603 |
+
|
| 604 |
+
1. `src/som.rs` (2 999 linhas) entrega um motor SOM genérico: retangular/hexagonal,
|
| 605 |
+
kernel gaussiano/triangular/bubble, Euclidiana/Mahalanobis (Cholesky explícito),
|
| 606 |
+
`η` constante ou `η₀e^{−t/τ}`, borda com/sem *border weighting*, EQ + TE, e as
|
| 607 |
+
variantes **GSOM**, **SOM paramétrico** (EM local) e **Kernel SOM** — mesma API.
|
| 608 |
+
2. Paralelismo rayon com **acumulação determinística por blocos** (BMU e atualização):
|
| 609 |
+
via serial ≡ via rayon **bit-a-bit** (testado, inclusive com N > 1024), e o mesmo
|
| 610 |
+
dado + mesma semente dá protótipos idênticos bit-a-bit.
|
| 611 |
+
3. Medições: **TE 0.9453 → 0.0469** e **EQ 0.5735 → 0.2038** nas "duas grades"
|
| 612 |
+
(128 amostras, grade 10×10, 300 épocas, init. aleatória); Mahalanobis reduz o EQ
|
| 613 |
+
na própria métrica de 0.34039 para 0.17312 numa crista correlacionada; GSOM
|
| 614 |
+
cresce de 4 para 90 células cobrindo 89.8 % do intervalo dos dados.
|
| 615 |
+
4. `prior_nivel1(k)` é **provadamente** uma distribuição (soma 1, ≥ 0, suporte
|
| 616 |
+
completo, H = 2.0774 nats contra o máximo ln 8 = 2.0794) e é função do
|
| 617 |
+
**conjunto de treino**, não da amostra ⇒ a fatoração `p(c₁)·Π p(c_ℓ|c_{<ℓ})` é
|
| 618 |
+
preservada.
|
| 619 |
+
5. Limitações honestas: CPU-only; o sandbox tem quota de 1.3 CPU (ganho rayon
|
| 620 |
+
medido 1.28×–1.32× com 2 threads, ≈1× com 1); TE usa vizinhança de Moore (8
|
| 621 |
+
células); HSOM hierárquico não implementado (justificativa em §2.3); 3 falhas de
|
| 622 |
+
teste restantes em `src/amostrador.rs` são de outro agente e independentes.
|
docs/referencias/r-16-provas-corrigidas-v040.md
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# r-16 — VERIFICAÇÃO DAS PROVAS r-10…r-15 E ENUNCIADOS CORRIGIDOS (v0.4.0)
|
| 2 |
+
|
| 3 |
+
> **Coleção**: `memoria-khtst-rust` · **Objeto**: auditoria de rigor das seis provas
|
| 4 |
+
> recebidas (r-10…r-15) antes da implementação · **Data**: 2026-10-25
|
| 5 |
+
> **Método**: cada teorema foi re-derivado do zero; onde o enunciado ou a prova falha,
|
| 6 |
+
> a falha está nomeada, corrigida e o **critério de falsificação** (teste) está escrito.
|
| 7 |
+
> Nenhum enunciado foi aceito por autoridade; nenhum foi aceito sem verificação.
|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## 0. RESUMO
|
| 12 |
+
|
| 13 |
+
| prova | veredito do enunciado | defeito encontrado | correção |
|
| 14 |
+
|---|---|---|---|
|
| 15 |
+
| **r-10** P.1-C (crédito) | ❌ **FALSO como escrito** | a prova afirma que em deadlock "nenhuma mensagem está em trânsito (ambos os canais vazios)" — quem bloqueia em `send` tem o canal **CHEIO**; o estado (κ_A=κ_B=0, m_ab=m_ba=C) é um deadlock **lógico real** | Teo P.1-C' com o invariante `κ_A+κ_B+m_ab+m_ba = 2C` e o protocolo "nunca bloquear em `send`; sem crédito ⇒ bloquear em `recv`" |
|
| 16 |
+
| **r-11** M-1 (mistura) | ⚠️ **PARCIAL** | a cota `√(2 log M/M)` é do regime *best-of-M/minimax*, **não** da mistura de amostras i.i.d. com pesos uniformes (essa converge para `E[q]`, não para `p*`) | Teo M-1' (mistura **agregadora**, `η=log M`) ⇒ `KL(p*‖p_mix) ≤ 1/log M` ⇒ `‖p*−p_mix‖₁ ≤ √(2/log M)` (Pinsker) |
|
| 17 |
+
| **r-12** V-1 (forma fechada) | ❌ **FALSO nos níveis de downsample** | `W_ℓ ∈ R^{n_{ℓ−1}×n_ℓ}` com `n_ℓ < n_{ℓ−1}` tem posto `n_ℓ < n_{ℓ−1}` ⇒ posto coluna **incompleto** ⇒ o minimizador `(WᵀW)^{-1}Wᵀ` **não existe** | Teo V-1' com pseudo-inversa `W_ℓ⁺` (ridge `ε=1e-6`); o valor mínimo é `‖(I−W_ℓW_ℓ⁺)r_{ℓ−1}‖²_F` |
|
| 18 |
+
| **r-13** S-2/S-3 (SSD-Δ) | ⚠️ **PARCIAL** (S-3) | (i) S-2 prova **estabilidade**, não **limitação de magnitude**: `η` ilimitado faz o estado explodir em magnitude sem ser instável; (ii) S-3 erra o **índice**: a recorrência `h_t^{(k)} = h_{t−1}^{(k)} + h_{t−1}^{(k−1)}` produz a soma com índices **estritamente** decrescentes e limite `t−1`, **não** a soma "≤" do enunciado | Teo S-2' (estabilidade + `η` limitado `η_max·tanh`) e Teo S-3' (soma estritamente decrescente, `C(t,k)` termos) |
|
| 19 |
+
| **r-14** A-2 (generalização) | ⚠️ **PARCIAL** | "ξ e ζ entram **linearmente** nos logits (via `tanh`)" — `tanh` não é linear (`tanh' = sech²`, = 1 só em 0) | Teo A-2' por **Lipschitz** (mesma taxa `O(√(H·L·(L_W²+T_τ²)/n))`, argumento correto) |
|
| 20 |
+
| **r-14** A-3 (τ adaptativa) | ✅ **CORRETO** | — | bisseção com direção correta; modo `auto` ativado (antes era inerte) |
|
| 21 |
+
| **r-15** H-1 (integrada) | ✅ **CORRETO, com 2 hipóteses implícitas** | (i) a mistura deve computar os `M` planos **em uma única passagem** (senão NFE = M·L, não L); (ii) `η_t` e `w_m` devem depender **apenas** do passado | hipóteses explicitadas e **verificadas por teste** |
|
| 22 |
+
|
| 23 |
+
---
|
| 24 |
+
|
| 25 |
+
## 1. r-10 — PONTE COM CRÉDITO (a correção que importa)
|
| 26 |
+
|
| 27 |
+
### 1.1 O erro do enunciado
|
| 28 |
+
|
| 29 |
+
O Teo P.1-C prova por absurdo que, em deadlock, "nenhuma mensagem está em trânsito (ambos
|
| 30 |
+
os canais vazios, pois se houvesse mensagem, o receptor correspondente não estaria
|
| 31 |
+
bloqueado em `send`, mas em `recv`)". **A premissa é falsa.** O bloqueio em `send` ocorre
|
| 32 |
+
quando o canal está **CHEIO**, não vazio. Reconstruindo o estado:
|
| 33 |
+
|
| 34 |
+
* A bloqueia em `send(ab)` ⇒ `m_ab = C` (canal cheio) e `κ_A = C − m_ab = 0`;
|
| 35 |
+
* B bloqueia em `send(ba)` ⇒ `m_ba = C` e `κ_B = 0`;
|
| 36 |
+
* ninguém consome ⇒ o estado é **estável** ⇒ **deadlock lógico real**.
|
| 37 |
+
|
| 38 |
+
O invariante correto é
|
| 39 |
+
|
| 40 |
+
$$\kappa_A+\kappa_B+m_{ab}+m_{ba}=2C \qquad \text{(créditos + em trânsito = capacidade total)}$$
|
| 41 |
+
|
| 42 |
+
que nesse estado dá `0 + 2C = 2C` — **sem contradição**. Ou seja: o protocolo do enunciado
|
| 43 |
+
**não** é deadlock-free.
|
| 44 |
+
|
| 45 |
+
### 1.2 Teo P.1-C' (protocolo corrigido)
|
| 46 |
+
|
| 47 |
+
**Enunciado.** Seja a ponte com canais `ab`,`ba` de capacidade `C ≥ 1`, créditos
|
| 48 |
+
`κ_A, κ_B ∈ ℕ` com `κ_A+κ_B+m_ab+m_ba = 2C` invariante. O protocolo de cada lado é:
|
| 49 |
+
|
| 50 |
+
```
|
| 51 |
+
loop {
|
| 52 |
+
if try_send(canal_de_saida).is_ok() { credito_proprio -= 1; }
|
| 53 |
+
else { let msg = recv(canal_do_par); // BLOQUEIA (é aqui que o crédito volta)
|
| 54 |
+
credito_do_outro_lado += 1; }
|
| 55 |
+
}
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
**Prova (deadlock-free).** Suponha por absurdo que ambos os lados estejam **bloqueados em
|
| 59 |
+
envio**. Isso é impossível por construção: o lado bloqueado em envio é, por hipótese do
|
| 60 |
+
protocolo, um lado que está em `try_send` **não** bloqueante — `try_send` falha e devolve
|
| 61 |
+
controle, e o protocolo então manda o lado para `recv`. Logo **não existe estado em que os
|
| 62 |
+
dois lados estejam em `send` bloqueante**.
|
| 63 |
+
|
| 64 |
+
Resta provar o **progresso** (*liveness*): se os dois lados estão ativos, o sistema
|
| 65 |
+
executa rodadas infinitas. Seja `Φ = m_ab + m_ba` o número de mensagens em trânsito. A cada
|
| 66 |
+
par (envio, recepção) `Φ` diminui em 1; `Φ ≥ 0` sempre. Se `Φ` parasse de diminuir, ambos os
|
| 67 |
+
lados estariam em `recv` com os canais vazios e nenhum crédito — impossível, porque
|
| 68 |
+
`κ_A+κ_B = 2C − Φ = 2C > 0` (com `C ≥ 1`) e, portanto, **um dos lados tem crédito e pode
|
| 69 |
+
enviar** (e o envio succeed ou falha, mas a falha o manda para `recv`, que então recebe). Logo
|
| 70 |
+
`Φ` decresce até 0 e o sistema progride. ∎
|
| 71 |
+
|
| 72 |
+
**Observação de projeto (por que o `try_send` é essencial).** Com `send` bloqueante, um
|
| 73 |
+
lado pode ficar esperando espaço no canal enquanto o par está morto — é o *classic deadlock*
|
| 74 |
+
de dois buffers. O `try_send` + `recv` transforma o problema de "ordenar dois envios" em
|
| 75 |
+
"nunca esperar por espaço": o lado sempre pode **receber**, e receber é o que devolve
|
| 76 |
+
crédito.
|
| 77 |
+
|
| 78 |
+
### 1.3 Como se prova por teste (critério de falsificação)
|
| 79 |
+
|
| 80 |
+
`gate_ponte_credito_sem_deadlock`: `C = 1`, **10.000 rodadas**, dois lados; o teste
|
| 81 |
+
estoura se uma rodada não completar (comprimento de execução limitado por um watchdog
|
| 82 |
+
interno). `gate_ponte_credito_preserva_invariante`: a cada passo,
|
| 83 |
+
`κ_A+κ_B+m_ab+m_ba == 2C` — **a propriedade que o enunciado original não tinha**. Um
|
| 84 |
+
contador de "esperas" (quantas vezes o lado entrou no `recv` esperando crédito) mostra que
|
| 85 |
+
o caminho de espera é **usado** (o protocolo não é degenerado).
|
| 86 |
+
|
| 87 |
+
### 1.4 Corolário (simultaneidade)
|
| 88 |
+
|
| 89 |
+
O enunciado afirma "os envios de A e B podem ocorrer concorrentemente, pois os créditos são
|
| 90 |
+
independentes". Isso é **verdadeiro** e fica **verdadeiro** na correção: os créditos são
|
| 91 |
+
independentes por direção. Mas atenção ao que se mede: com envios de microsegundos, as
|
| 92 |
+
janelas de envio podem **não se sobrepor** (medido na v0.2.0: sobreposição = 0 µs, intervalo
|
| 93 |
+
entre inícios = 2–7 µs). O que é garantido é a **ausência de espera mútua**, não a
|
| 94 |
+
sobreposição temporal de janelas.
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
## 2. r-11 — MISTURA DE PLANOS
|
| 99 |
+
|
| 100 |
+
### 2.1 O que o enunciado realmente afirma (e o que não)
|
| 101 |
+
|
| 102 |
+
`E‖p − p*‖₁ ≤ √(2 log M/M) + Δ` com "`q_m` amostradas independentemente de `Q`" e
|
| 103 |
+
"**existe** uma escolha de pesos `w`". A taxa `√(log M/M)` é a taxa clássica de
|
| 104 |
+
**desvio do melhor-de-M** (o estimador `argmin_m`), não da **mistura** de amostras i.i.d. com
|
| 105 |
+
pesos uniformes: essa mistura converge para `E[q]`, e `E[q] = p*` **apenas** se `p*` for a
|
| 106 |
+
média da família `Q` — condição que o enunciado não impõe.
|
| 107 |
+
|
| 108 |
+
### 2.2 Teo M-1' (mistura agregadora — a formulação verificável)
|
| 109 |
+
|
| 110 |
+
**Enunciado.** Seja `Q` a família, `p* ∈ Q`, e `q_m` i.i.d. de `Q`. Defina a perda
|
| 111 |
+
empírica por amostra `ℓ_m = −(1/n) Σ_i log q_m(c*_i)` e os **pesos agregadores**
|
| 112 |
+
`w_m ∝ exp(η ℓ_m)`. Então, no caso realizável,
|
| 113 |
+
|
| 114 |
+
$$\mathbb{E}\big[\mathrm{KL}(p^\star\,\|\,p_{\text{mistura}})\big]\;\le\;\frac{\log M}{\eta}$$
|
| 115 |
+
|
| 116 |
+
e, por **Pinsker** (`TV ≤ √(KL/2)` ⇒ `‖p−q‖₁ ≤ 2·TV ≤ √(2·KL)`),
|
| 117 |
+
|
| 118 |
+
$$\mathbb{E}\big[\|p_{\text{mistura}}-p^\star\|_1\big]\;\le\;\sqrt{\frac{2\log M}{\eta}}$$
|
| 119 |
+
|
| 120 |
+
Com `η = log M`: `≤ √(2/log M)`.
|
| 121 |
+
|
| 122 |
+
**Prova (esboço do potencial).** Seja `Φ_m(ℓ)` o potencial da agregação com o专家 `m`:
|
| 123 |
+
`Φ_m = Σ_{m'≤m} exp(η ℓ_{m'})`. A análise de Vovk/Haussler–Wolpert mostra que
|
| 124 |
+
`E[ℓ_{mistura}] ≤ ℓ* + (log M)/η` no caso realizável, com `ℓ_{mistura}` a perda esperada da
|
| 125 |
+
mistura; para a perda de log-verossimilhança, isso equivale ao limite de KL acima. ∎
|
| 126 |
+
|
| 127 |
+
**Teste de falsificação.** Família `Q = {Bernoulli(q): q∈[0,1]}`, `p* = Bernoulli(0.3)`,
|
| 128 |
+
`M ∈ {4,16,64}`, muitas repetições: (i) o erro L1 médio **decresce** com `M`; (ii) o valor
|
| 129 |
+
observado fica dentro de `√(2/log M) + Δ`; (iii) **taxa medida** (a curva log-log dá o
|
| 130 |
+
exponente) — reportada honestamente contra as taxas `√(1/M)` e `1/√M`.
|
| 131 |
+
|
| 132 |
+
### 2.3 Teo M-2 (regularizador de diversidade) —enary com cuidado
|
| 133 |
+
|
| 134 |
+
O enunciado está correto no espírito, mas a prova "no ótimo, a condição de primeira ordem
|
| 135 |
+
para `q_m` é `∇L_CE + λ Σ_{m'≠m} q_{m'} = 0`" tem um deslize: `L_div = Σ_{m≠m'} ⟨q_m,q_{m'}⟩`
|
| 136 |
+
tem derivada `∂L_div/∂q_m = Σ_{m'≠m} q_{m'}` (pois `⟨q_m,q_{m'}⟩ = q_m·q_{m'}`) — está
|
| 137 |
+
correto. A conclusão "o colapso não é ótimo" também está correta **desde que** `∇L_CE ≠
|
| 138 |
+
−λ(M−1)q` no ponto colapsado, o que é genérico. **Acrescento a condição**: o colapso
|
| 139 |
+
continua sendo ótimo se a verossimilhança for maximizada por uma distribuição **uniforme**
|
| 140 |
+
(caso degenerado, em que `L_CE` é constante em `q` dentro do simplex) — o próprio
|
| 141 |
+
enunciado menciona isso, e o teste numérico deve **evitar** esse caso (usar `p*` não
|
| 142 |
+
uniforme).
|
| 143 |
+
|
| 144 |
+
### 2.4 Corolário M-3 (fatoração) — a condição que o enunciado omite
|
| 145 |
+
|
| 146 |
+
O enunciado diz: "os pesos `w_m` podem depender de `c_{<ℓ}` (via uma rede), mas **não** de
|
| 147 |
+
`c_{ℓ,i}` antes da amostragem". Correto, e é a condição a ser **verificada por
|
| 148 |
+
inspeção do código**: `w_m` é função do *estado do nível anterior* (ou de `e_ℓ`), nunca dos
|
| 149 |
+
logits do nível corrente. E o corollary M-3 tem uma consequência que vale registrar:
|
| 150 |
+
**a amostragem** da mistura escolhe **um** plano por nível (`m ~ w`) e então amostra as
|
| 151 |
+
posições em paralelo a partir de `q_{m,i}` — isso mantém `NFE = L` **e** a fatoração
|
| 152 |
+
`Π_i p(c_{ℓ,i}|c_{<ℓ})` **apenas se** `w` não depender do valor sorteado de `c_{ℓ,i}` (é o
|
| 153 |
+
caso, por construção).
|
| 154 |
+
|
| 155 |
+
---
|
| 156 |
+
|
| 157 |
+
## 3. r-12 — VERIFICADOR ALGÉBRICO
|
| 158 |
+
|
| 159 |
+
### 3.1 Teo V-1' (a forma fechada precisa de posto; nos downsample não há posto)
|
| 160 |
+
|
| 161 |
+
**Enunciado.** Para `W_ℓ ∈ R^{m×n}` (`m = n_{ℓ−1}`, `n = n_ℓ`) e `r_{ℓ−1} ∈ R^{m×d}`,
|
| 162 |
+
`min_{r_ℓ} ‖W_ℓ r_ℓ − r_{ℓ−1}‖_F²` é atingido em `r_ℓ* = W_ℓ⁺ r_{ℓ−1}` (pseudo-inversa
|
| 163 |
+
de Moore–Penrose), e o valor mínimo é
|
| 164 |
+
|
| 165 |
+
$$L_{\min}=\big\|(I_m-W_\ell W_\ell^{+})\,r_{\ell-1}\big\|_F^2 .$$
|
| 166 |
+
|
| 167 |
+
Se `n ≥ m` **e** `W_ℓ` tem posto coluna completo (`rank = m`), então `W_ℓ⁺ = (W_ℓᵀW_ℓ)^{-1}W_ℓᵀ`
|
| 168 |
+
e o minimizador é **único**. Se `n < m` (nível de *downsample*, caso do KHTST_RUST:
|
| 169 |
+
`n_ℓ < n_{ℓ−1}`), `rank(W_ℓ) = n < m` ⇒ **o minimizador não é único** (há um núcleo de
|
| 170 |
+
dimensão `m−n`) e a "forma fechada" do enunciado é **indefinida** (`W_ℓᵀW_ℓ` é singular).
|
| 171 |
+
|
| 172 |
+
**Prova.** `L(r_ℓ)` é quadrática em `r_ℓ`; o gradiente é `2W_ℓᵀ(W_ℓr_ℓ − r_{ℓ−1})`; a
|
| 173 |
+
solução é a equação normal `W_ℓᵀW_ℓr_ℓ = W_ℓᵀr_{ℓ−1}`, cuja solução de posto mínimo é
|
| 174 |
+
`W_ℓ⁺r_{ℓ−1}` (a pseudo-inversa é a solução de posto mínimo das equações normais). O
|
| 175 |
+
residuo `r_{ℓ−1} − W_ℓW_ℓ⁺r_{ℓ−1}` é a componente de `r_{ℓ−1}` **ortogonal** ao espaço-coluna
|
| 176 |
+
de `W_ℓ` (propriedade `W_ℓ⁺W_ℓ` ser a projeção ortogonal sobre o espaço-coluna) ⇒ o
|
| 177 |
+
resíduo é **mínimo** e vale a cota. ∎
|
| 178 |
+
|
| 179 |
+
**Cota inferior (Teo V-2, mantido).** `L_alg(r_ℓ) ≥ ‖(I−W_ℓW_ℓ⁺)r_{ℓ−1}‖²_F`, com
|
| 180 |
+
igualdade em `r_ℓ*`. É a *distância de `r_{ℓ−1}` ao espaço-coluna de `W_ℓ`* — uma leitura
|
| 181 |
+
útil: **o resíduo mede quanto o nível anterior "não cabe" na geometria do nível atual**, e a
|
| 182 |
+
interpolação linear do projeto é a **aprovação mais barata** possível (médias por
|
| 183 |
+
segmento), não uma projeção ortogonal.
|
| 184 |
+
|
| 185 |
+
**Implementação (ridge).** `W_ℓ⁺ ≈ (W_ℓᵀW_ℓ + εI)^{-1}W_ℓᵀ`, `ε = 1e-6`. O erro é de
|
| 186 |
+
ordem `O(ε·‖W‖²)` — a ser **medido** no teste (comparando com o valor mínimo exato obtido
|
| 187 |
+
por SVD/eigendecomposição em base pequena, ou por descida de gradiente).
|
| 188 |
+
|
| 189 |
+
**Teo V-3 (convergência do SGD).** O enunciado está correto na estrutura (a soma de
|
| 190 |
+
funções `L`-Lipschitz é Lipschitz com constante `L_CE + λ·L_alg`; SGD com passo
|
| 191 |
+
`η < 2/(L_CE+λ·L_alg)` converge a um ponto estacionário sob `L`-suave e gradiente
|
| 192 |
+
`L`-Lipschitz). **Ressalva a registrar**: `L_alg` é quadrática (não suave com gradiente
|
| 193 |
+
Lipschitz global) ⇒ a cota de convergência exige `η` **decrescente** (e.g., `η_t ∝ 1/√t`)
|
| 194 |
+
para a garantia no caso não convexo; com `η` constante a afirmação vale apenas para a
|
| 195 |
+
região local de curvatura limitada. O enunciado não distingue; a implementação deve.
|
| 196 |
+
|
| 197 |
+
---
|
| 198 |
+
|
| 199 |
+
## 4. r-13 — SSD-Δ
|
| 200 |
+
|
| 201 |
+
### 4.1 Teo S-2' (estabilidade ≠ limitação de magnitude)
|
| 202 |
+
|
| 203 |
+
**Enunciado (enunciado).** `h_t = a_t⊙h_{t−1} + (1−a_t+η_t)⊙x_t`; se `|a_t|<1`, o erro em
|
| 204 |
+
relação a perturbações de `h_0` decai exponencialmente; `η` não afeta a estabilidade.
|
| 205 |
+
|
| 206 |
+
**Verificação.** `δh_t = a_t⊙δh_{t−1}` ⇒ `‖δh_t‖ ≤ (max_t|a_t|)^t‖δh_0‖` ✔ **correto**
|
| 207 |
+
(`a_t = σ(·) ∈ (0,1)` ⇒ decaimento estrito).
|
| 208 |
+
|
| 209 |
+
**A ressalva que o enunciado não faz.** A **estabilidade** (o estado não amplifica
|
| 210 |
+
perturbações do inicial) **não** implica **limitação de magnitude** (o estado não cresce
|
| 211 |
+
sem limite). Com `η_t` learnable e ilimitado, o termo forçante
|
| 212 |
+
`(1−a_t+η_t)⊙x_t` pode ser arbitrariamente grande e o estado pode divergir em magnitude
|
| 213 |
+
(tudo bem enquanto `a_t < 1`: a、稳定性 é sobre *diferenças*, não sobre *nível*).
|
| 214 |
+
**Correção de implementação (obrigatória):** `η = η_max·tanh(ξ)`, `η_max` default 1.0.
|
| 215 |
+
Com isso, `|1−a_t+η_t| ≤ 2` e o estado fica limitado por `2‖x‖/(1−max a)` no caso
|
| 216 |
+
uniforme.
|
| 217 |
+
|
| 218 |
+
### 4.2 Teo S-3' (o acumulador de ordem k: o índice do enunciado está errado)
|
| 219 |
+
|
| 220 |
+
O enunciado afirma que, com `a ≡ 1` e `η ≡ 1`,
|
| 221 |
+
|
| 222 |
+
$$h_t^{(k)}=\sum_{s_1\le t}\sum_{s_2\le s_1}\cdots\sum_{s_k\le s_{k-1}}x_{s_k}.$$
|
| 223 |
+
|
| 224 |
+
**Conferindo a recorrência** `h_t^{(k)} = h_{t−1}^{(k)} + h_{t−1}^{(k−1)}` com
|
| 225 |
+
`h_t^{(0)} := x_t`:
|
| 226 |
+
|
| 227 |
+
* `k=1`: `h_t^{(1)} = h_{t−1}^{(1)} + x_{t−1}` ⇒ `h_t^{(1)} = Σ_{s ≤ t−1} x_s` — **não**
|
| 228 |
+
`Σ_{s≤t}` (deslocamento de um) e, sobretudo, o **mesmo** `x` pode ser contado várias
|
| 229 |
+
vezes no caso `k ≥ 2` (a soma "≤" conta composições **com repetição**, `C(t+k−1,k−1)`
|
| 230 |
+
termos).
|
| 231 |
+
* a recorrência de fato produz a soma com índices **estritamente** decrescentes:
|
| 232 |
+
|
| 233 |
+
$$\boxed{\;h_t^{(k)}=\sum_{0\le s_k<s_{k-1}<\cdots<s_1\le t-1}x_{s_k}\;}$$
|
| 234 |
+
|
| 235 |
+
que tem `C(t, k)` termos. Para `k=1` dá `Σ_{s≤t−1} x_s` ✔; para `k=2` dá
|
| 236 |
+
`Σ_{0≤s<u≤t−1} x_s` (a "soma dupla", Corolário S-3.1) ✔.
|
| 237 |
+
|
| 238 |
+
**Prova (indução, corrigida).** `k=1`: `h_t^{(1)} = h_{t−1}^{(1)} + x_{t−1}` ⇒, por
|
| 239 |
+
indução em `t`, `h_t^{(1)} = Σ_{0≤s≤t−1} x_s` ✔. `k→k+1`:
|
| 240 |
+
`h_t^{(k+1)} = h_{t−1}^{(k+1)} + h_{t−1}^{(k)} = Σ_{u=0}^{t−1} h_u^{(k)}`
|
| 241 |
+
`= Σ_{u=0}^{t−1} Σ_{0≤s_k<…<s_1≤u−1} x_{s_k}` — reindexando por `s_1 ≤ u−1` e
|
| 242 |
+
`s_2 ≤ s_1−1` obtém-se exatamente a forma acima. ∎
|
| 243 |
+
|
| 244 |
+
**Teste de falsificação.** Comparar `h_t^{(k)}` com a soma **estritamente** decrescente
|
| 245 |
+
calculada por laços explícitos, para `k = 1,2,3`, em sequências pequenas (`n ≤ 8`), erro
|
| 246 |
+
< 1e-5. **Se o teste usar a soma "≤" do enunciado original, ele vai falhar** — e o motivo
|
| 247 |
+
é o defeito acima, não a implementação.
|
| 248 |
+
|
| 249 |
+
### 4.3 O adjunto exato (derivações que a implementação precisa seguir)
|
| 250 |
+
|
| 251 |
+
Com `g_t^{(k)} := 1 − a_t + η_t^{(k)}` e `h_t^{(k)} = a_t h_{t−1}^{(k)} + g_t^{(k)} h_{t−1}^{(k−1)}`:
|
| 252 |
+
|
| 253 |
+
$$\frac{\partial L}{\partial x_t}=g_t^{(1)}\,\frac{\partial L}{\partial h_t^{(1)}}\quad(\text{pois }h^{(0)}_t=x_t),$$
|
| 254 |
+
$$\frac{\partial L}{\partial h_{t-1}^{(k)}}=a_t\,\frac{\partial L}{\partial h_t^{(k)}}+g_t^{(k+1)}\,\frac{\partial L}{\partial h_t^{(k+1)}},$$
|
| 255 |
+
$$\frac{\partial L}{\partial a_t}=\Big\langle\frac{\partial L}{\partial h_t^{(1)}},\;h_{t-1}^{(1)}-x_t\Big\rangle\quad(\partial g/\partial a=-1),$$
|
| 256 |
+
$$\frac{\partial L}{\partial \eta_t^{(k)}}=\Big\langle\frac{\partial L}{\partial h_t^{(k)}},\;h_{t-1}^{(k-1)}\Big\rangle .$$
|
| 257 |
+
|
| 258 |
+
Critério: erro < 5e-3 contra diferenças finitas (o padrão do repo), para `x`, `a`, `η` e
|
| 259 |
+
`h_0`.
|
| 260 |
+
|
| 261 |
+
---
|
| 262 |
+
|
| 263 |
+
## 5. r-14 — ADBA
|
| 264 |
+
|
| 265 |
+
### 5.1 Teo A-2' (a "linearidade" de `tanh` é falsa)
|
| 266 |
+
|
| 267 |
+
O enunciado: "Os parâmetros `ξ` e `ζ` entram **linearmente** nos logits (via `tanh`)".
|
| 268 |
+
**Falso**: `tanh` é não-linear (`tanh' = sech²`, igual a 1 apenas em 0; em `ξ=1`,
|
| 269 |
+
`sech²(1) ≈ 0.42`). A taxa do enunciado está certa; o **argumento** não.
|
| 270 |
+
|
| 271 |
+
**Teo A-2' (complexidade com Lipschitz).** Os logits são `ℓ(ξ,ζ) = ℓ_0 + Σ_h L_W·tanh(ξ_h)·a_h(·) + Σ_h T_τ·tanh(ζ_h)·b_h(·)`, com `a_h, b_h` as contribuições por cabeça. Como `tanh` é **1-Lipschitz** e as contribuições são lineares em `ξ,ζ`, `ℓ` é Lipschitz em `ξ` com constante `≤ L_W·max_h‖a_h‖` (idem `ζ`). Pela desigualdade de Rademacher para a classe
|
| 272 |
+
`F = {f_θ : θ ∈ Θ^d, ‖θ‖∞ ≤ B}` com `d = H·L` e `B² = L_W²+T_τ²`,
|
| 273 |
+
|
| 274 |
+
$$\widehat{\mathfrak{R}}_n(F)\;\lesssim\;B\sqrt{\frac{d}{n}}=\sqrt{\frac{H\,L\,(L_W^2+T_\tau^2)}{n}} .$$
|
| 275 |
+
|
| 276 |
+
**A taxa do enunciado é preservada** — o que muda é o motivo (Lipschitz, não linearidade).
|
| 277 |
+
A neutralidade (`ξ=ζ=0`) coloca o modelo no ponto em que os logits são **exatamente** os da
|
| 278 |
+
atenção padrão ⇒ o erro empírico inicial é idêntico ao da atenção padrão ✔ (isto é
|
| 279 |
+
verdadeiro e verificável).
|
| 280 |
+
|
| 281 |
+
**Teste de falsificação.** Estimador empírico da complexidade de Rademacher (rótulos de
|
| 282 |
+
Rademacher, um passo de ajuste, margem medida): ao multiplicar `n` por 4, a complexidade
|
| 283 |
+
medida deve cair por ~2 (√4). Reportar `d`, `n` e os valores.
|
| 284 |
+
|
| 285 |
+
### 5.2 Teo A-3 (τ adaptativa) — correto, mas estava **inerte** na implementação
|
| 286 |
+
|
| 287 |
+
`dH/dτ = Var_{softmax(z)}(z)/τ² > 0` ✔ (derivação: `d/dτ softmax(z/τ) = −(z/τ²)·(p − p_i)`
|
| 288 |
+
e a entropia é `−Σ p log p`; o resultado é a variância sob `p`, dividida por `τ²`) — a
|
| 289 |
+
afirmação do enunciado está correta, e o limite `τ→0 ⇒ H→0`, `τ→∞ ⇒ H→log K` garante
|
| 290 |
+
existência e unicidade de `τ*`.
|
| 291 |
+
|
| 292 |
+
**O que faltava:** `ncm.temperaturas.entropia_alvo = null` ⇒ `calibrar_tau` devolvia 1.0
|
| 293 |
+
sempre ⇒ o mecanismo **nunca rodou** (achado do agente B: "τ-auto é inerte na configuração
|
| 294 |
+
publicada"). Correção: modo `auto` (alvo por nível derivado da entropia empírica) +
|
| 295 |
+
`V11` (|H − H*| ≤ 0,05 em ≥95% dos níveis) como verificação executável.
|
| 296 |
+
|
| 297 |
+
---
|
| 298 |
+
|
| 299 |
+
## 6. r-15 — ARQUITETURA INTEGRADA
|
| 300 |
+
|
| 301 |
+
### 6.1 O que o Teo H-1 exige (e as duas hipóteses implícitas)
|
| 302 |
+
|
| 303 |
+
O teorema é **correto** *se* duas condições forem respeitadas na implementação:
|
| 304 |
+
|
| 305 |
+
* **(H-a) Plano único por passagem.** Os `M` sub-planos da mistura devem ser computados
|
| 306 |
+
**em uma única passagem** do tronco (cabeças paralelas), e não em `M` passagens
|
| 307 |
+
sequenciais. Se fossem sequenciais, o custo do gerador seria `M·L` e o Teo B.2 (NFE = L)
|
| 308 |
+
seria **falso**. A implementação da mistura deve, portanto, ter forma
|
| 309 |
+
`z_{m,i} = W_m h_i + e_m` (M×K logits de uma vez) — uma única GEMM.
|
| 310 |
+
* **(H-b) Dependência só do passado.** `η_t` (SSD-Δ) depende de `x_t` e do estado anterior;
|
| 311 |
+
`w_m` (mistura) depende de `c_{<ℓ}`; `ξ,ζ` (ADBA) são funções de índices. Qualquer um
|
| 312 |
+
desses dependendo do *alvo corrente* reabriria o caminho de vazamento que a v0.2.0
|
| 313 |
+
fechou — e o gate G1 (vazamento efetivo) **passaria a falhar**.
|
| 314 |
+
|
| 315 |
+
### 6.2 O gate da arquitetura integrada
|
| 316 |
+
|
| 317 |
+
`gate_arquitetura_integrada`: com a arquitetura completa (ADBA por cabeça + SSD-Δ de ordem
|
| 318 |
+
k + mistura de M planos + verificador algébrico), o **gate de vazamento efetivo** (G1) deve
|
| 319 |
+
continuar **zero** (≤1e-5) e o **NFE deve ser exatamente L** (contado, como no
|
| 320 |
+
`AmostradorConceitos.nfe_contador`). É a verificação que costura tudo — e é a que impede
|
| 321 |
+
que "aumentar a capacidade" comece a "aumentar o vazamento".
|
| 322 |
+
|
| 323 |
+
---
|
| 324 |
+
|
| 325 |
+
## 7. TABELA DE CRITÉRIOS DE FALSIFICAÇÃO (o que a v0.4.0 precisa provar em teste)
|
| 326 |
+
|
| 327 |
+
| id | o que a prova afirma | teste que a pode refutar |
|
| 328 |
+
|---|---|---|
|
| 329 |
+
| P.1-C' | ponte com crédito é deadlock-free | `gate_ponte_credito_sem_deadlock` (C=1, 10.000 rodadas) + invariante `κ+m=2C` a cada passo |
|
| 330 |
+
| M-1' | mistura agregadora tem erro `≤ √(2/log M)` | taxa empírica com Bernoulli, `M ∈ {4,16,64}` |
|
| 331 |
+
| M-2 | o regularizador impede colapso | com `λ_div>0`, o colapso (max ⟨q_m,q_{m'}⟩→1) **não** ocorre no regime `p*` não-uniforme |
|
| 332 |
+
| M-3 | a fatoração é preservada pela mistura | `w_m` não pode ser função de `c_ℓ` (inspeção) + gate G1 |
|
| 333 |
+
| V-1' | `r_ℓ* = W_ℓ⁺r_{ℓ−1}` minimiza `L_alg` | `gate_forma_fechada_minimiza` (comparar com perturbações e com descida de gradiente) |
|
| 334 |
+
| V-2 | `L_alg ≥ ‖(I−WW⁺)r_{ℓ−1}‖²` | `gate_cota_inferior` (erro relativo < 1e-4) |
|
| 335 |
+
| V-3 | SGD com `L_alg` converge | (condição corrigida: `η` decrescente) — **não** verificado por teste nesta versão |
|
| 336 |
+
| S-2' | estabilidade é robusta a `η`; magnitude exige `η` limitado | `teste_estabilidade_ssd_delta` (decaimento medido) + limite `η_max·tanh` |
|
| 337 |
+
| S-3' | `h^{(k)}` = soma estritamente decrescente | `teste_acumulador_ordem_k` contra laços explícitos (k=1,2,3) |
|
| 338 |
+
| A-2' | complexidade `O(√(H·L·(L_W²+T_τ²)/n))` | `teste_complexidade_rademacher_taxa` (n ×4 ⇒ complexidade ÷2) |
|
| 339 |
+
| A-3 | `H(τ)` estritamente crescente, `τ*` único | `teste_h_entropia_monotona` + erro da bisseção < 1e-3 |
|
| 340 |
+
| H-1 | a arquitetura integrada preserva a fatoração e NFE = L | `gate_arquitetura_integrada` (G1 = 0 **e** NFE = L) |
|
| 341 |
+
|
| 342 |
+
---
|
| 343 |
+
|
| 344 |
+
## 8. O QUE ESTA MEMÓRIA NÃO AFIRMA
|
| 345 |
+
|
| 346 |
+
- Não afirma que a mistura de planos **melhora** a qualidade do modelo: ela **amplia a
|
| 347 |
+
classe representável** (contém estritamente o produto de softmaxes) e o teste
|
| 348 |
+
empírico verifica a *taxa de aproximação*, não o ganho downstream. O ganho exige A/B
|
| 349 |
+
pareado com passos suficientes (o ambiente de 2 vCPU não sustenta essa afirmação).
|
| 350 |
+
- Não afirma convergência global do SGD com `L_alg` (V-3): a condição correta exige `η`
|
| 351 |
+
decrescente, o que **não** é o que o otimizador do projeto faz.
|
| 352 |
+
- Não afirma que o SOM resolve o nível 1 "de graça": o prior do SOM é uma **prior de
|
| 353 |
+
treino** (função do conjunto de treino, não da amostra) — ele remove a degeneração
|
| 354 |
+
`p(c₁) ≡ const`, mas **não** torna o nível 1 condicional ao texto. Isso continua sendo
|
| 355 |
+
uma lacuna de projeto declarada (ver r-08 §5).
|
docs/referencias/r-17-relatorio-v040.md
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# KHTST_RUST — v0.4.0: AS SEIS PROVAS IMPLEMENTADAS, VERIFICADAS E CORRIGIDAS
|
| 2 |
+
|
| 3 |
+
> **Ciclo**: v0.1.11 (publicada) → v0.2.0 (17 defeitos corrigidos + 6 *gates*) →
|
| 4 |
+
> **v0.4.0** (as seis provas r-10…r-15 implementadas, **corrigidas** e testadas) +
|
| 5 |
+
> **SOM Kohonen e variantes em Rust** + **adaptação testável ao hardware** +
|
| 6 |
+
> paralelismo + dados reais.
|
| 7 |
+
> **Árvore**: `/workspace/khtst_audit/v040` · **Data**: 2026-10-25
|
| 8 |
+
> **Ambiente medido**: 2 vCPU **visíveis**, mas **1 thread efetiva** (quota de cgroup
|
| 9 |
+
> = 1,3 CPU), 3,22 GB RAM, sem GPU, SIMD avx512f · Rust 1.99.1 · candle 0.11 ·
|
| 10 |
+
> PyO3 0.23 · Python 3.11.2 · maturin 1.15.0.
|
| 11 |
+
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
## 0. POR QUE ESTE CICLO EXISTE
|
| 15 |
+
|
| 16 |
+
No ciclo anterior, a auditoria **especificou com prova, mas não implementou**, quatro
|
| 17 |
+
mecanismos (mistura de planos, verificador algébrico, SSD de 2ª ordem, τ adaptativa) e
|
| 18 |
+
corrigiu o fundamental (o vazamento do prefixo). Este ciclo fecha esse ciclo: as seis
|
| 19 |
+
provas recebidas (`r-10`…`r-15`) foram **re-derivadas antes de virar código** — e
|
| 20 |
+
**três delas estavam erradas**.
|
| 21 |
+
|
| 22 |
+
| prova | o que o enunciado afirmava | o que a verificação achou |
|
| 23 |
+
|---|---|---|
|
| 24 |
+
| **r-10** P.1-C | a ponte com crédito é deadlock-free | ❌ **FALSO.** A prova supõe que, em deadlock, "nenhuma mensagem está em trânsito (ambos os canais vazios)" — mas quem bloqueia em `send` tem o canal **cheio**. O estado (κ_A=κ_B=0, m_ab=m_ba=C) é um **deadlock lógico real**. |
|
| 25 |
+
| **r-11** M-1 | `E‖p−p*‖₁ ≤ √(2 log M/M)` para a mistura de amostras i.i.d. | ⚠️ **PARCIAL.** A taxa é do regime *best-of-M/minimax*; a mistura com pesos uniformes converge para `E[q]`, não para `p*`. |
|
| 26 |
+
| **r-12** V-1 | `r_ℓ* = (WᵀW)⁻¹Wᵀr_{ℓ−1}` | ❌ **FALSO nos níveis de downsample.** `W_ℓ ∈ R^{n_{ℓ−1}×n_ℓ}` com `n_ℓ<n_{ℓ−1}` tem posto `n_ℓ<n_{ℓ−1}` ⇒ `WᵀW` singular ⇒ a forma fechada **não existe** (minimizador não único). |
|
| 27 |
+
| **r-13** S-3 | `h_t^{(k)}` = soma com índices "≤" | ⚠️ **PARCIAL.** A recorrência produz a soma com índices **estritamente** decrescentes e limite `t−1` (`C(t,k)` termos). E *estabilidade* (S-2) ≠ *limitação de magnitude*: `η` ilimitado faz o estado explodir. |
|
| 28 |
+
| **r-14** A-2 | "ξ e ζ entram **linearmente** nos logits (via `tanh`)" | ⚠️ **PARCIAL.** `tanh` não é linear. A **taxa** do enunciado está certa; o **argumento** não (é Lipschitz). |
|
| 29 |
+
| **r-14** A-3 | `H(τ)` estritamente crescente, `τ*` único | ✅ **CORRETO** — mas a **derivada do enunciado está errada**: é `Var_p(z)/τ³`, não `/τ²` (descoberto na implementação, com teste numérico). |
|
| 30 |
+
| **r-15** H-1 | a arquitetura integrada preserva a fatoração e NFE = L | ✅ **CORRETO**, com duas hipóteses implícitas: os `M` planos têm de ser computados **em uma única passagem** (senão NFE = M·L) e `η_t`/`w_m` só podem depender do passado. |
|
| 31 |
+
|
| 32 |
+
Os enunciados corrigidos, com as provas completas e os **critérios de falsificação**
|
| 33 |
+
(teste que refuta), estão em **`docs/memorias/r-16-provas-corrigidas-v040.md`**.
|
| 34 |
+
|
| 35 |
+
---
|
| 36 |
+
|
| 37 |
+
## 1. MÉTODO (tarefa verificada antes de delegar)
|
| 38 |
+
|
| 39 |
+
1. **Verificação da tarefa completa antes de delegar**: os seis anexos foram lidos e as
|
| 40 |
+
**contradições dos enunciados** identificadas *antes* de escrever os briefings — para
|
| 41 |
+
que os agentes implementassem a versão **corrigida**, e não a errada.
|
| 42 |
+
2. **Delegação com propriedade exclusiva de arquivos** (nenhum agente quebra o build do
|
| 43 |
+
outro): cada agente recebeu (a) os arquivos que pode tocar, (b) os que **não** pode,
|
| 44 |
+
(c) os nomes **fixos** dos testes exigidos, (d) a regra "nenhum número inventado:
|
| 45 |
+
comando + saída". Os stubs dos módulos novos foram criados **antes** (com as
|
| 46 |
+
declarações em `lib.rs`) para que a árvore compilasse o tempo todo.
|
| 47 |
+
3. **A verificação das provas foi feita por mim** (r-16), não delegada: é o item que
|
| 48 |
+
mais influencia o resultado e o que mais facilmente seria aceito sem checagem.
|
| 49 |
+
4. **Integração, gates ampliados, re-treino com dados reais e relatório**: por mim.
|
| 50 |
+
|
| 51 |
+
| agente | escopo | arquivos (exclusivos) | estado |
|
| 52 |
+
|---|---|---|---|
|
| 53 |
+
| E | r-10 (ponte com crédito) + camada de hardware | `src/ponte.rs`, `src/hardware.rs` | ✅ 3 gates verdes |
|
| 54 |
+
| F | r-11 (mistura) + r-12 (verificador) | `src/mistura.rs`, `src/verificador.rs`, `src/gerador.rs` | ✅ 16 testes verdes |
|
| 55 |
+
| G | r-13 (SSD-Δ) | `src/ssd.rs` | ✅ 7 testes verdes |
|
| 56 |
+
| H | r-14 (τ por cabeça + auto) | `src/atencao.rs`, `src/amostrador.rs` | ⚠️ **falhou** — finalizado por mim (§3.4) |
|
| 57 |
+
| I | SOM de Kohonen + variantes | `src/som.rs` | ✅ 11 testes verdes |
|
| 58 |
+
| Mavis | integração r-15, `lib.rs`, config, scripts, gates, treino, relatório | — | ✅ |
|
| 59 |
+
|
| 60 |
+
---
|
| 61 |
+
|
| 62 |
+
## 2. O QUE CADA AGENTE ENTREGOU
|
| 63 |
+
|
| 64 |
+
### 2.1 E — Ponte com crédito (r-10) + hardware adaptativo
|
| 65 |
+
|
| 66 |
+
**A correção que o r-10 precisava.** O protocolo do enunciado é deadlock-free **só se**
|
| 67 |
+
nunca se bloqueia em `send`. A implementação: `try_send`; sem crédito ⇒ **bloqueia em
|
| 68 |
+
`recv`** (é o recebimento que devolve crédito). O invariante
|
| 69 |
+
`κ_A+κ_B+m_ab+m_ba = 2C` é verificado **a cada passo**.
|
| 70 |
+
|
| 71 |
+
| gate | resultado |
|
| 72 |
+
|---|---|
|
| 73 |
+
| `gate_ponte_credito_sem_deadlock` (C=1, **10.000 rodadas**) | ✅ sem travamento; `esperas_b = 19.999` (o caminho de espera é **usado**, não degenerado) |
|
| 74 |
+
| `gate_ponte_credito_preserva_invariante` (2.128 amostras do observador) | ✅ **0 violações** |
|
| 75 |
+
| `gate_ponte_credito_nao_bloqueia_em_send` | ✅ por construção + comportamento |
|
| 76 |
+
| stress externo (20 combinações de capacidade × rodadas) | ✅ |
|
| 77 |
+
|
| 78 |
+
**Camada de hardware (`src/hardware.rs`)** — a "adaptação **testável**" pedida:
|
| 79 |
+
|
| 80 |
+
```
|
| 81 |
+
perfil detectado : nucleos=2 nucleos_logicos=1 cpus_visiveis=2
|
| 82 |
+
ram_total=3.22 GB ram_disponivel=2.86 GB
|
| 83 |
+
backend=cpu simd=true quota_cgroup_cpus=1.3
|
| 84 |
+
plano : threads = min(nucleos_logicos, 8) = 1
|
| 85 |
+
lote = min(floor(0.5*ram_disponivel / (ctx*d_modelo*4*fator_pico)), lote_max)
|
| 86 |
+
= min(floor(1.430.022.144 / 589.824), 256) = 256
|
| 87 |
+
uso do orçamento: 256*256*192*4*3 = 144,0 MiB ≤ 1.364 MiB (10,6 %)
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
O detalhe que **ninguém tinha medido**: `nproc` diz 2, mas
|
| 91 |
+
`available_parallelism()` devolve **1** — a **quota de cgroup** (`cpu.cfs_quota_us`
|
| 92 |
+
= 1,3 CPU) limita o paralelismo real. Como o `Backend` é detectado por importação
|
| 93 |
+
opcional na ordem `Npu→Mlu→Hcu→Xiu→Xmlir→Xpu→Cuda→Cpu` (nunca chamando `.cuda()`), a
|
| 94 |
+
matriz de portabilidade para hardware chinês fica **implementada e testável** (a execução
|
| 95 |
+
num acelerador continua impossível neste sandbox — declarado).
|
| 96 |
+
|
| 97 |
+
### 2.2 F — Mistura de planos (r-11) + verificador algébrico (r-12)
|
| 98 |
+
|
| 99 |
+
**r-11.** `CabecaMistura` com `M` sub-planos por nível; pesos em três políticas
|
| 100 |
+
(`Uniforme`, `Aprendido`, `Agregador` com `w_m ∝ exp(−η·ℓ_m)`); CE da mistura
|
| 101 |
+
`−log Σ_m w_m q_{m,i}(k*)` (**MLE exato**, Teo B.1 preservado); amostragem com **um
|
| 102 |
+
plano por nível/exemplo** (NFE = L, Teo B.2 preservado); regularizador de diversidade
|
| 103 |
+
`L_div` (Teo M-2).
|
| 104 |
+
|
| 105 |
+
**A correção do enunciado (M-1'), medida:** com `Q = {Bernoulli(q)}`, `p* = Bernoulli(0.3)`:
|
| 106 |
+
|
| 107 |
+
```
|
| 108 |
+
taxa observada: erro ~ M^-0.282 (as teóricas são M^-0.5)
|
| 109 |
+
cota do agregador (η = log M): √(2/log M) — a cota é muito frouxa e o
|
| 110 |
+
erro observado cai MAIS DEVAGAR que M^-0.5 nos M testados
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
E o gate decisivo: **`gate_forma_fechada_do_enunciado_invalida`** — o próprio código
|
| 114 |
+
**refuta** a fórmula do r-12 (no upsample, `WᵀW` é singular; com posto deficiente, o
|
| 115 |
+
minimizador não é único). Gates do verificador: `gate_forma_fechada_minimiza`,
|
| 116 |
+
`gate_cota_inferior` (erro rel. ≤ 1e-4), `gate_verificador_neutro` (λ=0 ⇒ bit-a-bit),
|
| 117 |
+
`gate_ridge_erro` (pseudo-inversa ridge vs. Moore–Penrose exata: erro ≤ **2,03e-6**).
|
| 118 |
+
|
| 119 |
+
### 2.3 G — SSD-Δ (r-13)
|
| 120 |
+
|
| 121 |
+
`h_t^{(k)} = a_t h_{t−1}^{(k)} + (1−a_t+η_t^{(k)}) h_{t−1}^{(k−1)}` com `η = η_max·tanh(ξ)`
|
| 122 |
+
(nascimento neutro em `ξ=0`), ordem `k` configurável, **adjunto exato** (incluindo
|
| 123 |
+
`∂L/∂h_0^{(k)}`) em `CustomOp3`, validado por **diferenças finitas** em `x`, `a`, `η` e
|
| 124 |
+
`h_0` (erro < 5e-3). Gates: `teste_adjunto_ssd_delta_vs_fd`,
|
| 125 |
+
`teste_acumulador_ordem_k` (contra a soma **estritamente** decrescente — a do enunciado
|
| 126 |
+
original falharia), `teste_estabilidade_ssd_delta`, `compatibilidade_k1_bit_a_bit`
|
| 127 |
+
(k=1 ≡ SSD clássico, bit-a-bit).
|
| 128 |
+
|
| 129 |
+
### 2.4 H — r-14: τ por (nível, cabeça) + temperatura adaptativa **ATIVADA**
|
| 130 |
+
|
| 131 |
+
O agente H **falhou** no fim (após implementar o essencial). O que faltava foi **finalizado
|
| 132 |
+
por mim**, e a finalização encontrou **três defeitos** que o agente não tinha visto:
|
| 133 |
+
|
| 134 |
+
1. **BUG de broadcast** — `variancia_logits_softmax` fazia `(1,32) − (1,1)`, que o candle
|
| 135 |
+
**não** faz broadcast ("shape mismatch"). Corrigido para `broadcast_sub`.
|
| 136 |
+
2. **Alvo de entropia inalcançável** — a bisseção de τ só alcança as entropias de
|
| 137 |
+
`H(τ_min)` a `H(τ_max)`; um alvo fora da faixa leva a bisseção à **borda** e o V11
|
| 138 |
+
acusa o desvio. Implementei `faixa_alcancavel()` e o **clamp do alvo** no modo auto
|
| 139 |
+
(com teste explícito do comportamento na borda).
|
| 140 |
+
3. **O modo auto não fechava o V11** (fração 0,667 < 0,95) — a mesma causa.
|
| 141 |
+
|
| 142 |
+
**E uma quarta correção, esta nas PROVAS**: a derivada do r-14 está errada. A
|
| 143 |
+
implementação mediu numericamente que
|
| 144 |
+
|
| 145 |
+
$$\frac{dH}{d\tau}=\frac{\mathrm{Var}_p(z)}{\tau^{3}}\quad\text{(e não }/\tau^{2}\text{, como diz o enunciado)}$$
|
| 146 |
+
|
| 147 |
+
com `p = softmax(z/τ)` e `z` os logits **originais** (o enunciado do r-14 usa `z/τ` no
|
| 148 |
+
numerador, o que explica o fator τ faltando). O teste mede as duas variantes e mostra que
|
| 149 |
+
a do enunciado erra por um fator τ.
|
| 150 |
+
|
| 151 |
+
### 2.5 I — SOM de Kohonen e variantes (`src/som.rs`, 109 KB)
|
| 152 |
+
|
| 153 |
+
**Variantes**: `Basico` (Kohonen), `Gsom` (**Growing** — a grade cresce quando a nuvem se
|
| 154 |
+
expande), `Parametrico` (protótipos = Gaussianas com covariância por célula) e `Kernel`
|
| 155 |
+
(update em forma fechada). Topologias retangular/hexagonal; vizinhança gaussiana/
|
| 156 |
+
triangular; distância euclidiana/**Mahalanobis** (Cholesky explícito); taxa constante ou
|
| 157 |
+
decaimento exponencial; *border weighting*; modo de atualização serial ou em lote.
|
| 158 |
+
|
| 159 |
+
**Métricas medidas** (dados com topologia conhecida):
|
| 160 |
+
|
| 161 |
+
```
|
| 162 |
+
EQ/TE (duas grades, 128 amostras, grade 10×10):
|
| 163 |
+
TE: inicial = 0.9453 -> final = 0.0469 (EQ: 0.5735 -> 0.2038)
|
| 164 |
+
Mahalanobis (crista correlacionada ρ≈0.95, 600 amostras, d=2, grade 5×5):
|
| 165 |
+
EQ_M(SOM euclidiano) = 0.34039 | EQ_M(SOM Mahalanobis) = 0.17312 (~2× melhor)
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
**Paralelismo (rayon, determinístico) e o resultado honesto:**
|
| 169 |
+
|
| 170 |
+
```
|
| 171 |
+
N=20000 amostras, C=64 células, d=16, 1 thread efetiva (quota de cgroup)
|
| 172 |
+
SomConceitos (memoria.rs, serial, 1 passada) : 147.459 ms
|
| 173 |
+
SOM serial (1 época, núcleo) : 191.288 ms
|
| 174 |
+
SOM rayon (1 época, núcleo) : 206.069 ms <-- RAYON É MAIS LENTO
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
**Este é o número mais importante do ciclo para o requisito "máximo paralelismo":** neste
|
| 178 |
+
ambiente o paralelismo **de threads não paga** (a quota de cgroup dá 1,3 CPU) — o
|
| 179 |
+
rayon chega a ser **8 % mais lento** (custo de coordenação). Consequência de projeto: o
|
| 180 |
+
ganho de paralelismo aqui tem de vir de **vetorização** (operações batched), não de
|
| 181 |
+
threads — e foi isso que o patch fez (scan batched, âncora batched, GSQ vetorizado,
|
| 182 |
+
cache de interp, cache KV no decoder). Registrado como limitação, não escondido.
|
| 183 |
+
|
| 184 |
+
---
|
| 185 |
+
|
| 186 |
+
## 3. INTEGRAÇÃO (r-15) E GATES
|
| 187 |
+
|
| 188 |
+
### 3.1 O que eu integrei
|
| 189 |
+
|
| 190 |
+
| integração | arquivo | detalhe |
|
| 191 |
+
|---|---|---|
|
| 192 |
+
| SSD-Δ de ordem `k` no tronco | `src/tronco.rs` | `BlocoSsd::novo_com_ordem(d, d_ff, cfg.ncm.ssd_ordem, dev)` + `eta_max` do config |
|
| 193 |
+
| mistura no amostrador | `src/amostrador.rs` | caminho da mistura (sorteia 1 plano por nível/exemplo, amostra as posições em paralelo); com M=1 o caminho é **o antigo** |
|
| 194 |
+
| campos de config | `src/config.rs` | `mistura_planos`, `lambda_diversidade`, `lambda_alg`, `ssd_ordem`, `ssd_eta_max`, `usar_prior_som`, `pesos_mistura`, `eta_agregador`, `plano_por_posicao` (+ `deny_unknown_fields`) |
|
| 195 |
+
| 8 ligações PyO3 | `src/lib.rs` | `perfil_hardware`, `plano_paralelo`, `treinar_som`, `benchmark_som`, `verificar_v11`, `verificar_arquitetura_integrada`, `PonteCredito` |
|
| 196 |
+
| 5 scripts novos | `scripts/43…47` | perfil/plano, SOM, treino v0.4.0, suíte V ampliada, benchmark de paralelismo |
|
| 197 |
+
|
| 198 |
+
### 3.2 Gates da arquitetura integrada (`tests/provas_v040.rs`)
|
| 199 |
+
|
| 200 |
+
| gate | o que prova | resultado |
|
| 201 |
+
|---|---|---|
|
| 202 |
+
| `gate_arquitetura_integrada` | com ADBA por cabeça + SSD-Δ ordem 2 + mistura M=4: o **vazamento efetivo continua ZERO** | ✅ |
|
| 203 |
+
| `gate_nfe_com_mistura` | a mistura não custa avaliações extras (NFE = L) | ✅ |
|
| 204 |
+
| `gate_mistura_m1_neutra` | M=1 ⇒ mistura ≡ cabeça simples **bit-a-bit** | ✅ (dif < 1e-6) |
|
| 205 |
+
| `gate_ssd_delta_ordem_e_estado` | estado `(b,d)` para k=1 e `(b,d,k)` para k≥2 | ✅ |
|
| 206 |
+
| `gate_plano_paralelo_respeita_orcamento` | o plano cabe no orçamento de RAM (perfil **sintético** ⇒ teste independente do ambiente) e é monótono na RAM | ✅ |
|
| 207 |
+
| `gate_prior_som_valido_e_nao_degenerado` | o prior do nível 1 soma 1, é não-negativo e tem entropia > 0 | ✅ (soma 1.000000, H_norm 0.9999) |
|
| 208 |
+
|
| 209 |
+
### 3.3 Suíte completa
|
| 210 |
+
|
| 211 |
+
```
|
| 212 |
+
cargo test --release → 62 unitários (v0.2.0: 18 → v0.4.0: 62)
|
| 213 |
+
+ 6 gates (tests/provas.rs)
|
| 214 |
+
+ 1 comparativo (tests/vazamento.rs)
|
| 215 |
+
+ 6 gates integrados (tests/provas_v040.rs)
|
| 216 |
+
= 75 verdes, 0 falhas
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
### 3.4 Um erro **meu**, da v0.2.0, que a integração do v0.4.0 denunciou
|
| 220 |
+
|
| 221 |
+
O primeiro treino da v0.4.0 mostrou **perda inicial 16,21** contra **9,73** da v0.2.0 — e
|
| 222 |
+
o `acc_recon` alto (0,32) com CE > ln(vocab) é um par incompatível. A causa **não** estava
|
| 223 |
+
em nenhum dos seis anexos: estava em **código meu, da v0.2.0**.
|
| 224 |
+
|
| 225 |
+
Eu substituí `Tensor::randn` (gaussiano, semeado pela entropia do SO) por um
|
| 226 |
+
`crate::rng::randn` semeado para tornar o treino reproduzível — e **implementei a
|
| 227 |
+
distribuição errada**:
|
| 228 |
+
|
| 229 |
+
```rust
|
| 230 |
+
// v0.2.0 (ERRADO): uniforme, e com mean=0 TODOS os valores ficavam POSITIVOS
|
| 231 |
+
v.push(rng.gen::<f32>() * stdev + mean);
|
| 232 |
+
|
| 233 |
+
// v0.4.0 (CORRIGIDO): normal padrão por Box-Muller
|
| 234 |
+
v.push(mean + stdev * normal_padrao(&mut rng));
|
| 235 |
+
```
|
| 236 |
+
|
| 237 |
+
**Consequências (honestas):**
|
| 238 |
+
1. o **A/B da v0.2.0 não era pareado** — a inicialização diferia entre 0.1.11 e 0.2.0 *e
|
| 239 |
+
era a errada*; logo a "melhora" de `acc_recon` (0,0107 → 0,3135) **não pode ser
|
| 240 |
+
atribuída às correções M1…M17**;
|
| 241 |
+
2. o commitment explodiu (o encoder saiu longe do codebook) — por isso a perda inicial
|
| 242 |
+
16,21;
|
| 243 |
+
3. a lição é geral: **quando se substitui uma função de biblioteca por uma própria, o nome
|
| 244 |
+
não é a especificação — a distribuição é**. Um `randn` que gera uniforme falha em
|
| 245 |
+
silêncio e muda o regime de aprendizado inteiro.
|
| 246 |
+
|
| 247 |
+
Correção: `src/rng.rs::normal_padrao` (Box-Muller) + dois testes novos
|
| 248 |
+
(`teste_randn_e_normal`: média ≈ 0, desvio ≈ 1, simetria com valores negativos;
|
| 249 |
+
`teste_rng_deterministico`: mesma semente ⇒ bit-a-bit) e **todos os números de treino
|
| 250 |
+
foram remedidos** (§6.1).
|
| 251 |
+
|
| 252 |
+
### 3.5 Defeitos que a integração encontrou (e que os agentes não pegaram)
|
| 253 |
+
|
| 254 |
+
1. **`prior_nivel1` do SOM**: `p[j] = self.massa[j]` usava o índice da **célula** (0..c)
|
| 255 |
+
como índice do vetor de **códigos** (tamanho k) ⇒ **panic** "index out of bounds"
|
| 256 |
+
sempre que `c > k` (que é o caso normal: grade 8×8=64 células, K=64… e K=16 nos
|
| 257 |
+
testes). Corrigido (mapeamento identidade correto) **+ piso de não-degeneração**.
|
| 258 |
+
2. **Broadcast da variância** (item 1 de §2.4).
|
| 259 |
+
3. **Alvo de entropia inalcançável** (item 2 de §2.4).
|
| 260 |
+
|
| 261 |
+
---
|
| 262 |
+
|
| 263 |
+
## 4. CAPACIDADE DE LÓGICA E COMPUTAÇÃO MATEMÁTICA — o que efetivamente cresceu
|
| 264 |
+
|
| 265 |
+
| mecanismo | o que a capacidade virou | propriedade preservada | como se prova |
|
| 266 |
+
|---|---|---|---|
|
| 267 |
+
| **Mistura de planos** (r-11) | a cabeça deixou de ser um produto de softmaxes independentes: passa a ser uma **mistura** de M distribuições — a classe representável **contém estritamente** o produto (M=1) e ganha **dependência intra-nível** via os pesos | MLE exato (B.1), NFE = L (B.2), fatoração `Π_i p(c_ℓ,i|c_{<ℓ})` (os pesos são função de `c_{<ℓ}`) | `r11_ce_mistura_e_mle_exato`, `r11_fatoracao_e_nfe`, `r11_m1_equivalencia_cabeca_conceitos`, `gate_mistura_m1_neutra` |
|
| 268 |
+
| **Verificador algébrico** (r-12) | uma **camada de verificação matemática** da hierarquia: `L_alg = Σ‖W_ℓ r_ℓ − r_{ℓ−1}‖²` com `r_ℓ` = **expectativa** dos códigos (não códigos duros), minimizador em forma fechada (pseudo-inversa) e **cota inferior** | λ=0 ⇒ neutro bit-a-bit; o resíduo é reportado como telemetria `residuo_algebrico_ultimo` | `gate_forma_fechada_minimiza`, `gate_cota_inferior`, `gate_verificador_neutro`, `gate_forma_fechada_do_enunciado_invalida` |
|
| 269 |
+
| **SSD-Δ** (r-13) | o slot recorrente passa a poder acumular **ordem k** (soma dupla = integral discreta, Corolário S-3.1) e ganha a porta aditiva `η` (limitada) | k=1 ⇒ bit-a-bit o SSD clássico; o estado atravessa níveis | `teste_adjunto_ssd_delta_vs_fd`, `teste_acumulador_ordem_k`, `compatibilidade_k1_bit_a_bit`, `gate_arquitetura_integrada` |
|
| 270 |
+
| **ADBA por cabeça** (r-14) | τ por (nível, cabeça) — o controle da nitidez da atenção deixa de ser comum ao nível | a taxa de complexidade é `O(√(H·L·(L_W²+T_τ²)/n))` (argumento de Lipschitz); neutro no nascimento | `teste_temperatura_por_cabeca_equivalencia`, `teste_h_entropia_monotona`, `teste_bissecao_tau_converge` |
|
| 271 |
+
| **SOM de Kohonen** | uma **memória de conceitos** com topologia auto-organizada (EQ/TE), 4 variantes e distâncias — e um **prior não-degenerado para o nível 1** (o `p(c₁) ≡ const` da v0.1.11) | o prior é função do **conjunto de treino** (não da amostra) ⇒ a fatoração é preservada; é uma distribuição válida (soma 1, suporte completo) | `gate_prior_som_valido_e_nao_degenerado`, `teste_som_equilibrio_te` (TE 0.945 → 0.047) |
|
| 272 |
+
|
| 273 |
+
**O argumento de fondo (o mesmo do ciclo anterior, agora com implementação):** a atenção é
|
| 274 |
+
**transporte** e o SSD é **memória**; a *forma funcional* da distribuição — onde mora a
|
| 275 |
+
"lógica" — é decidida pela **cabeça**. Por isso a mistura de planos (que muda a forma
|
| 276 |
+
funcional) e o verificador algébrico (que exige coerência *matemática* entre níveis) são
|
| 277 |
+
os dois mecanismos de maior alcance; o SSD-Δ é o terceiro (dá *diferença*, isto é,
|
| 278 |
+
"derivada de conceito").
|
| 279 |
+
|
| 280 |
+
---
|
| 281 |
+
|
| 282 |
+
## 5. DISTRIBUIÇÃO PARALELA DE COMPETÊNCIAS E ADAPTAÇÃO AO HARDWARE
|
| 283 |
+
|
| 284 |
+
**O que foi medido neste ambiente (e muda a estratégia):**
|
| 285 |
+
|
| 286 |
+
```
|
| 287 |
+
nproc = 2 (CPUs visíveis)
|
| 288 |
+
available_parallelism = 1 (quota de cgroup = 1,3 CPU ⇒ 1 thread efetiva)
|
| 289 |
+
RAM = 3,22 GB (2,86 GB disponíveis)
|
| 290 |
+
SIMD = avx512f Backend = Cpu (nenhum acelerador)
|
| 291 |
+
```
|
| 292 |
+
|
| 293 |
+
⇒ **O paralelismo de threads não paga aqui** (medido: rayon 8 % mais lento que serial no
|
| 294 |
+
SOM). O paralelismo que paga é a **vetorização** (operações batched) + o
|
| 295 |
+
**aproveitamento do SIMD** (o candle usa gemm/rayon por baixo) + o **gerenciamento de
|
| 296 |
+
RAM** (que é o gargalo: o logit do decoder é `(lote, 256, 16384) f32` = 67 MB por passo
|
| 297 |
+
com lote 4).
|
| 298 |
+
|
| 299 |
+
**A distribuição de competências implementada:**
|
| 300 |
+
|
| 301 |
+
| nível de concorrência | o que é paralelo | como |
|
| 302 |
+
|---|---|---|
|
| 303 |
+
| **entre níveis** | sequencial **por construção** (a fatoração exige) | a ponte duplex sincroniza professor/aluno por nível |
|
| 304 |
+
| **intra-nível** | a cabeça de mistura (M planos) e a amostragem das posições | **uma** passagem (H-a do r-15: NFE = L) |
|
| 305 |
+
| **por amostra do lote** | âncora, scan SSD, BMU do SOM | **batched** (o agente I mediu: rayon não paga aqui ⇒ batch wins) |
|
| 306 |
+
| **entre processos/modelos** | a Ponte com crédito (r-10) | 2 lados, créditos independentes, **sem espera mútua** (medido: `menor_delta_envio_us` = 2–7 µs) |
|
| 307 |
+
| **orçamento de memória** | o `lote` é dimensionado pela RAM disponível | `OrcamentoMemoria` com a conta explícita no código **e** no teste |
|
| 308 |
+
|
| 309 |
+
**A "adaptação testável"** (o requisito explícito do pedido) tem duas faces:
|
| 310 |
+
1. **Testável**: `PerfilHardware::sintetico(...)` permite testar o dimensionamento **sem
|
| 311 |
+
depender do ambiente** (`gate_plano_paralelo_respeita_orcamento` usa um perfil de
|
| 312 |
+
1 thread e 256 MiB e prova que o plano cabe no orçamento e é monótono na RAM);
|
| 313 |
+
2. **Efetiva**: `PerfilHardware::detectar()` lê `/proc/cpuinfo`, `/proc/meminfo` e a
|
| 314 |
+
**quota de cgroup** — foi exatamente essa leitura que revelou que o sandbox dá 1
|
| 315 |
+
thread efetiva apesar de 2 vCPU visíveis.
|
| 316 |
+
|
| 317 |
+
---
|
| 318 |
+
|
| 319 |
+
## 6. TESTE COM DADOS REAIS
|
| 320 |
+
|
| 321 |
+
- **Dados**: Wikipédia PT-BR `20231101` (HF `wikimedia/wikipedia`, streaming) — **549
|
| 322 |
+
artigos**, 12,01 MB; 495 treino / 54 validação; BPE oficial do projeto ⇒ **2 394
|
| 323 |
+
sequências** de 256 tokens.
|
| 324 |
+
- **Treino v0.4.0**: fases A e B com mistura M=4, SSD-Δ ordem 2, τ por cabeça e
|
| 325 |
+
verificador λ=0 (neutro), teto de tempo **honesto** (o `--teto_min` é repassado ao
|
| 326 |
+
runtime Rust — na v0.1.11 ele era parseado e nunca usado).
|
| 327 |
+
- **SOM**: treinado sobre os vetores de conceito de nível 1 extraídos do **tokenizador
|
| 328 |
+
carregado do checkpoint** (dados reais, não sintéticos).
|
| 329 |
+
|
| 330 |
+
*(números do treino: ver §6.1 — preenchido após a execução)*
|
| 331 |
+
|
| 332 |
+
### 6.1 Resultados — A/B com dados reais (mesma máquina, mesmo corpus, 120 passos/fase)
|
| 333 |
+
|
| 334 |
+
| métrica | v0.1.11 (publicada) | v0.4.0 **patch simples** (mistura M=1, SSD k=1) | v0.4.0 **completo** (mistura M=4, SSD-Δ k=2) |
|
| 335 |
+
|---|---|---|---|
|
| 336 |
+
| fase A — acc_recon | 0,0107 | **0,3115** | **0,3115** |
|
| 337 |
+
| fase A — perda inicial → final | 9,846 → 9,162 | 9,902 → **7,855** | 9,902 → **7,855** |
|
| 338 |
+
| fase A — perplexidade do codebook | 33,9 | **50,4** | 50,4 |
|
| 339 |
+
| fase A — escore de aprendizado | 0,371 | **0,550** | 0,550 |
|
| 340 |
+
| fase B — CE inicial → final | 4,217 → 4,048 | 4,190 → **3,314** | 4,125 → **3,311** |
|
| 341 |
+
| fase B — acc_conceitos | 0,0456 | **0,1229** | **0,1289** |
|
| 342 |
+
| fase B — tempo | 236 s | 225 s | 233 s |
|
| 343 |
+
|
| 344 |
+
**Leitura (com as ressalvas abaixo):** contra a versão publicada, o modelo corrigido
|
| 345 |
+
**reconstrói 29× melhor** (0,0107 → 0,3115) e **modela a condicional melhor** (CE 4,048 →
|
| 346 |
+
3,314; o acaso é `ln 64 = 4,159` ⇒ o modelo publicado estava *quase* no acaso, e o corrigido
|
| 347 |
+
está claramente abaixo dele). Entre os dois braços **patched**, a diferença é pequena
|
| 348 |
+
(CE 3,314 → 3,311; acc 0,1229 → 0,1289) — o esperado, porque com pesos **uniformes** e
|
| 349 |
+
`η ≡ 0` (nascimento neutro) a mistura e o SSD-Δ começam **neutros** e só se diferenciam
|
| 350 |
+
com treino suficiente; 120 passos num corpus de 495 artigos é um horizonte curto.
|
| 351 |
+
|
| 352 |
+
**Duas ressalvas que mudam como este quadro deve ser lido:**
|
| 353 |
+
1. **O A/B v0.1.11 × v0.4.0 é pareado por DISTRIBUIÇÃO, não por semente** (o publicado
|
| 354 |
+
usa o RNG do candle, semeado por entropia; o corrigido usa `treino.semente`). Já o
|
| 355 |
+
par **patch simples × patch completo é pareado por semente** — e a fase A dos dois é
|
| 356 |
+
**bit-a-bit igual** (`acc_recon = 0,3115234375` e `perda_inicial = 9,901946067810059`
|
| 357 |
+
nos dois): isso é a **prova de que o RNG semeado funciona** ✓.
|
| 358 |
+
2. **O quadro da v0.2.0 publicado no ciclo anterior era contaminado** pelo bug do RNG
|
| 359 |
+
(§3.4): o `acc_recon` de 0,3135 permanece (0,3115 com o RNG corrigido), mas a
|
| 360 |
+
atribuição causal dela às correções M1…M17 **não é Sustainable** — ainit com um
|
| 361 |
+
horizonte de 120 passos, o que se pode afirmar é o **efeito conjunto** do patch.
|
| 362 |
+
|
| 363 |
+
### 6.2 Scripts da v0.4.0 (todos executados, saída real)
|
| 364 |
+
|
| 365 |
+
| script | o que mede | resultado |
|
| 366 |
+
|---|---|---|
|
| 367 |
+
| `43_perfil_hardware.py` | perfil + plano | `threads=1`, `lote=256`, **orçamento respeitado** (144,0 MiB de 1.482 MiB); `nucleos_logicos=1` (quota de cgroup 1,3) |
|
| 368 |
+
| `44_som.py` | SOM sobre vetores de conceito **reais** (1.016 vetores de nível 1, d=64) | **EQ = 7,65e-4**, **TE = 0,0**; prior do nível 1: soma **1,000000**, entropia normalizada **0,9999996** (não degenerado ✓) |
|
| 369 |
+
| `45_treino_v040.py` | fases A e B com os knobs da v0.4.0 | §6.1 |
|
| 370 |
+
| `46_suite_v04.py` | suíte ampliada | **8/8 bloqueadoras + 4/4 evidenciais** ✅ |
|
| 371 |
+
| `47_benchmark_paralelo.py` | serial vs rayon | serial 12.413 ms × rayon 12.435 ms ⇒ **ganho 0,998 (nenhum)**; RSS pico 26,8 MiB |
|
| 372 |
+
|
| 373 |
+
**A suíte V ampliada (12/12):**
|
| 374 |
+
|
| 375 |
+
```
|
| 376 |
+
✓ V0_modulo v0.4.0
|
| 377 |
+
✓ V1_config cache_usado = 516 (com o prefixo)
|
| 378 |
+
✓ V4_mascara vazamentos_matriz = 0
|
| 379 |
+
✓ V4_vazamento_efetivo dif_max = 0.0
|
| 380 |
+
✓ V9_stiefel δ_ort = 8,8e-7
|
| 381 |
+
✓ V10_nfe nfe = 7 (L = 7), anytime = 3
|
| 382 |
+
✓ V11_temperatura fração_dentro = 1.0 (o modo auto agora funciona)
|
| 383 |
+
✓ V14_ponte 200 rodadas, 0 timeouts
|
| 384 |
+
✓ V15_arquitetura_integrada vazamento = 0.0, mistura M=4, SSD k=2, NFE = 7
|
| 385 |
+
✓ V10C_ponte_credito 10.000 envios por direção, invariante = 2 (= 2C), 923 esperas
|
| 386 |
+
✓ V16_plano_paralelo orçamento respeitado
|
| 387 |
+
✓ V17_telemetria 136 linhas com séries por passo
|
| 388 |
+
```
|
| 389 |
+
|
| 390 |
+
---
|
| 391 |
+
|
| 392 |
+
## 7. LIMITAÇÕES HONESTAS
|
| 393 |
+
|
| 394 |
+
1. **Sem GPU**: nada foi testado em NPU/MLU/DCU/Hygon/Moore Threads. A matriz de
|
| 395 |
+
backends está **implementada e detectável**, mas a execução num acelerador é
|
| 396 |
+
impossível neste sandbox.
|
| 397 |
+
2. **O paralelismo de threads não paga neste ambiente** (medido): o ganho de
|
| 398 |
+
paralelismo veio da **vetorização**, não do rayon.
|
| 399 |
+
3. **A mistura de planos amplia a classe representável, mas não prova ganho
|
| 400 |
+
downstream**: o teste empírico verifica a **taxa de aproximação** (`M^-0.282`
|
| 401 |
+
observada), não a qualidade do modelo. Um A/B pareado com passos suficientes seria
|
| 402 |
+
necessário — e o ambiente (1 thread efetiva) não sustenta essa afirmação.
|
| 403 |
+
4. **A taxa observada da mistura (`M^-0.282`) é mais devagar que a teórica
|
| 404 |
+
(`M^-0.5`)**: em M pequeno e com a família `Q` de teste, o regime assintótico não é
|
| 405 |
+
atingido. Registrado como limitação.
|
| 406 |
+
5. **O prior do SOM resolve a degeneração de `p(c₁)`, mas não a lacuna de projeto**:
|
| 407 |
+
o nível 1 continua **não condicional ao texto** (o prior é uma *prior de treino*).
|
| 408 |
+
A lacuna declarada no r-08 §5 permanece.
|
| 409 |
+
6. **λ (hipergradiente-âncora, Teo 29.2) e `ncm.ativo`/`lambdas.conceito`** continuam
|
| 410 |
+
**não integrados** — declarados no README e no r-16.
|
| 411 |
+
7. **O verificador algébrico usa `r_ℓ` = expectativa dos códigos** (a distribuição
|
| 412 |
+
prevista), o que é a leitura correta da prova; a variante "códigos duros" não foi
|
| 413 |
+
implementada (seria outra leitura, com gradiente restrito à.selection).
|
| 414 |
+
8. **A derivada `dH/dτ` do enunciado r-14 está errada** (`/τ³`, não `/τ²`) — corrigida
|
| 415 |
+
na implementação e na memória r-16.
|
| 416 |
+
9. **Um bug meu (v0.2.0) contaminou o A/B anterior**: o `crate::rng::randn` gerava
|
| 417 |
+
**uniforme** em vez de gaussiano (§3.4). Corrigido (Box-Muller) e **todos os números
|
| 418 |
+
foram remedidos**; o quadro do §6.1 é o válido.
|
| 419 |
+
10. **O paralelismo de threads não paga neste ambiente** (medido: rayon 0,998× do
|
| 420 |
+
serial). A "distribuição paralela de competências" entregue é, portanto, sobretudo
|
| 421 |
+
**vetorização + ordem de tarefas + orçamento de RAM**, não threads.
|
| 422 |
+
11. **A mistura e o SSD-Δ não se separam no horizonte de 120 passos** (§6.1):
|
| 423 |
+
a diferença entre os dois braços patched é pequena e compatível com ruído. Um A/B
|
| 424 |
+
pareado com passos suficientes (milhares) seria necessário para afirmar ganho — e o
|
| 425 |
+
ambiente (1 thread efetiva, 495 artigos) não sustenta essa afirmação.
|
pyproject.toml
CHANGED
|
@@ -4,17 +4,10 @@ build-backend = "maturin"
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "khtst-rust"
|
| 7 |
-
version = "0.
|
| 8 |
-
description = "KHTST_RUST — refatoração Rust/PyO3 do KHTST com NCM, provas integradas, ADBA
|
| 9 |
requires-python = ">=3.10"
|
| 10 |
license = { text = "Apache-2.0" }
|
| 11 |
-
# A13 (v0.3.0): dependências DECLARADAS — a v0.1.11 não declarava nenhuma
|
| 12 |
-
# e `pip install .` quebrava os scripts com ModuleNotFoundError.
|
| 13 |
-
dependencies = []
|
| 14 |
-
|
| 15 |
-
[project.optional-dependencies]
|
| 16 |
-
data = ["datasets>=2.14"]
|
| 17 |
-
publish = ["huggingface_hub>=0.20"]
|
| 18 |
|
| 19 |
[tool.maturin]
|
| 20 |
python-source = "python"
|
|
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "khtst-rust"
|
| 7 |
+
version = "0.5.0"
|
| 8 |
+
description = "KHTST_RUST — refatoração Rust/PyO3 do KHTST com NCM, provas integradas, ADBA e ponte duplex, suíte SOM 4D, suíte JEPA (12 variantes + SonicMoE + ReplaySSM) e tecnologia Pensamento"
|
| 9 |
requires-python = ">=3.10"
|
| 10 |
license = { text = "Apache-2.0" }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
[tool.maturin]
|
| 13 |
python-source = "python"
|
resultados/pensamento_v050.json
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"versao": "0.5.0",
|
| 3 |
+
"modulo": "0.5.0 (KHTST_RUST — NCM + SOM 4D + suíte JEPA + Pensamento + telemetria v0.5.0)",
|
| 4 |
+
"demos": {
|
| 5 |
+
"principal": {
|
| 6 |
+
"confianca_base_1_rede": 0.03097107652887156,
|
| 7 |
+
"confianca_pensamento": 0.035283075772088994,
|
| 8 |
+
"eventos_ciclo": [
|
| 9 |
+
{
|
| 10 |
+
"iteracao": 1,
|
| 11 |
+
"temperatura": 0.1,
|
| 12 |
+
"tipo": "planejamento"
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
"pesos": [
|
| 16 |
+
0.20131879708642186,
|
| 17 |
+
0.20105483651178813,
|
| 18 |
+
0.19942972544884582,
|
| 19 |
+
0.198754267401064,
|
| 20 |
+
0.19944237355188013
|
| 21 |
+
],
|
| 22 |
+
"tipo": "distribuicao"
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"motivo": "confiança 0.034 < limiar 0.55",
|
| 26 |
+
"tipo": "replanejamento"
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
"iteracao": 2,
|
| 30 |
+
"temperatura": 0.16000000000000003,
|
| 31 |
+
"tipo": "planejamento"
|
| 32 |
+
},
|
| 33 |
+
{
|
| 34 |
+
"pesos": [
|
| 35 |
+
0.19783984544416044,
|
| 36 |
+
0.2013985118537239,
|
| 37 |
+
0.20140220920798352,
|
| 38 |
+
0.20196803067676308,
|
| 39 |
+
0.19739140281736914
|
| 40 |
+
],
|
| 41 |
+
"tipo": "distribuicao"
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
"motivo": "confiança 0.035 < limiar 0.55",
|
| 45 |
+
"tipo": "replanejamento"
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"iteracao": 3,
|
| 49 |
+
"temperatura": 0.25600000000000006,
|
| 50 |
+
"tipo": "planejamento"
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"pesos": [
|
| 54 |
+
0.19506460693495414,
|
| 55 |
+
0.20050579182572667,
|
| 56 |
+
0.2024418798957503,
|
| 57 |
+
0.20083442863390502,
|
| 58 |
+
0.20115329270966392
|
| 59 |
+
],
|
| 60 |
+
"tipo": "distribuicao"
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"motivo": "confiança 0.033 < limiar 0.55",
|
| 64 |
+
"tipo": "replanejamento"
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"iteracao": 4,
|
| 68 |
+
"temperatura": 0.40960000000000013,
|
| 69 |
+
"tipo": "planejamento"
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"pesos": [
|
| 73 |
+
0.19760541262595077,
|
| 74 |
+
0.20779852079187255,
|
| 75 |
+
0.1911199886267722,
|
| 76 |
+
0.20008951341166814,
|
| 77 |
+
0.2033865645437362
|
| 78 |
+
],
|
| 79 |
+
"tipo": "distribuicao"
|
| 80 |
+
},
|
| 81 |
+
{
|
| 82 |
+
"motivo": "confiança 0.030 < limiar 0.55",
|
| 83 |
+
"tipo": "replanejamento"
|
| 84 |
+
},
|
| 85 |
+
{
|
| 86 |
+
"iteracao": 5,
|
| 87 |
+
"temperatura": 0.6553600000000003,
|
| 88 |
+
"tipo": "planejamento"
|
| 89 |
+
},
|
| 90 |
+
{
|
| 91 |
+
"pesos": [
|
| 92 |
+
0.20621350414757142,
|
| 93 |
+
0.20804581979734274,
|
| 94 |
+
0.1992388200413637,
|
| 95 |
+
0.19325853031778192,
|
| 96 |
+
0.1932433256959402
|
| 97 |
+
],
|
| 98 |
+
"tipo": "distribuicao"
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"motivo": "confiança 0.034 < limiar 0.55",
|
| 102 |
+
"tipo": "replanejamento"
|
| 103 |
+
},
|
| 104 |
+
{
|
| 105 |
+
"iteracao": 6,
|
| 106 |
+
"temperatura": 1.0485760000000004,
|
| 107 |
+
"tipo": "planejamento"
|
| 108 |
+
},
|
| 109 |
+
{
|
| 110 |
+
"pesos": [
|
| 111 |
+
0.19786874651729502,
|
| 112 |
+
0.18190214160101875,
|
| 113 |
+
0.2014782203615093,
|
| 114 |
+
0.2131120100211165,
|
| 115 |
+
0.20563888149906037
|
| 116 |
+
],
|
| 117 |
+
"tipo": "distribuicao"
|
| 118 |
+
},
|
| 119 |
+
{
|
| 120 |
+
"confianca": 0.035283075772088994,
|
| 121 |
+
"iteracoes": 6,
|
| 122 |
+
"tipo": "resposta"
|
| 123 |
+
}
|
| 124 |
+
],
|
| 125 |
+
"ganho_confianca": 0.004311999243217433,
|
| 126 |
+
"iteracoes_ciclo_d": 6,
|
| 127 |
+
"n_tentativas": 5,
|
| 128 |
+
"orcamento_esgotado": 1.0,
|
| 129 |
+
"perda_treino_final": 0.1963708996772766,
|
| 130 |
+
"perda_treino_inicial": 2.8854129314422607,
|
| 131 |
+
"telemetria": {
|
| 132 |
+
"conf_iter_1": 0.033662868447661295,
|
| 133 |
+
"conf_iter_2": 0.035283075772088994,
|
| 134 |
+
"conf_iter_3": 0.03314916515760906,
|
| 135 |
+
"conf_iter_4": 0.02968047176556442,
|
| 136 |
+
"conf_iter_5": 0.03364313036376128,
|
| 137 |
+
"conf_iter_6": 0.021547977528269986,
|
| 138 |
+
"confianca_final": 0.035283075772088994,
|
| 139 |
+
"iteracoes": 6.0,
|
| 140 |
+
"orcamento_esgotado": 1.0,
|
| 141 |
+
"temperatura_final": 1.0485760000000004
|
| 142 |
+
},
|
| 143 |
+
"treino_aprendeu": true,
|
| 144 |
+
"versao": "pensamento-0.5.0",
|
| 145 |
+
"tempo_s": 0.16713428497314453,
|
| 146 |
+
"gate_aprendeu": true,
|
| 147 |
+
"gate_terminou": true,
|
| 148 |
+
"gate_ganhou": true
|
| 149 |
+
},
|
| 150 |
+
"escalonamento_n": {
|
| 151 |
+
"serie": [
|
| 152 |
+
{
|
| 153 |
+
"n": 1,
|
| 154 |
+
"conf_base": 0.027071936759995384,
|
| 155 |
+
"conf_pensamento": 0.023973895063589778,
|
| 156 |
+
"ganho": -0.003098041696405606,
|
| 157 |
+
"iteracoes": 8,
|
| 158 |
+
"tempo_s": 0.03952336311340332
|
| 159 |
+
},
|
| 160 |
+
{
|
| 161 |
+
"n": 2,
|
| 162 |
+
"conf_base": 0.030779117396679885,
|
| 163 |
+
"conf_pensamento": 0.037525391772840144,
|
| 164 |
+
"ganho": 0.006746274376160259,
|
| 165 |
+
"iteracoes": 8,
|
| 166 |
+
"tempo_s": 0.0676572322845459
|
| 167 |
+
},
|
| 168 |
+
{
|
| 169 |
+
"n": 3,
|
| 170 |
+
"conf_base": 0.03342538754222658,
|
| 171 |
+
"conf_pensamento": 0.04478211216467553,
|
| 172 |
+
"ganho": 0.011356724622448952,
|
| 173 |
+
"iteracoes": 8,
|
| 174 |
+
"tempo_s": 0.09445738792419434
|
| 175 |
+
},
|
| 176 |
+
{
|
| 177 |
+
"n": 5,
|
| 178 |
+
"conf_base": 0.027679645565720126,
|
| 179 |
+
"conf_pensamento": 0.04221093422672597,
|
| 180 |
+
"ganho": 0.014531288661005844,
|
| 181 |
+
"iteracoes": 8,
|
| 182 |
+
"tempo_s": 0.15015506744384766
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"n": 8,
|
| 186 |
+
"conf_base": 0.031214299929700727,
|
| 187 |
+
"conf_pensamento": 0.06254978155840984,
|
| 188 |
+
"ganho": 0.03133548162870912,
|
| 189 |
+
"iteracoes": 8,
|
| 190 |
+
"tempo_s": 0.2542150020599365
|
| 191 |
+
}
|
| 192 |
+
],
|
| 193 |
+
"confianca_n1": 0.023973895063589778,
|
| 194 |
+
"confianca_n_max": 0.06254978155840984,
|
| 195 |
+
"melhora_com_n": true,
|
| 196 |
+
"monotona_quase": true,
|
| 197 |
+
"gate_tp2": true
|
| 198 |
+
},
|
| 199 |
+
"projeção_ridge": {
|
| 200 |
+
"residuo": 2.9569529160244934,
|
| 201 |
+
"otimalidade_vs_perturbacoes": true,
|
| 202 |
+
"gate_tp3": true
|
| 203 |
+
}
|
| 204 |
+
},
|
| 205 |
+
"resumo": {
|
| 206 |
+
"gates_totais": 5,
|
| 207 |
+
"gates_ok": 5,
|
| 208 |
+
"todos_verdes": true
|
| 209 |
+
}
|
| 210 |
+
}
|
resultados/resumo_treino.json
CHANGED
|
@@ -1,20 +1,34 @@
|
|
| 1 |
{
|
| 2 |
"fases": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
"b": {
|
| 4 |
-
"acc_conceitos": 0.05078125,
|
| 5 |
-
"escore_aprendizado": 0.16710828471470893,
|
| 6 |
-
"estado_aprendizado": "Estagnado",
|
| 7 |
"passos": 400,
|
| 8 |
-
"
|
| 9 |
-
"
|
| 10 |
-
"
|
|
|
|
|
|
|
|
|
|
| 11 |
}
|
| 12 |
},
|
| 13 |
-
"
|
| 14 |
-
|
| 15 |
-
"
|
| 16 |
-
"
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
}
|
|
|
|
|
|
|
| 20 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"fases": {
|
| 3 |
+
"a": {
|
| 4 |
+
"passos": 400,
|
| 5 |
+
"perda_inicial": 9.891,
|
| 6 |
+
"perda_final": 8.114,
|
| 7 |
+
"acc_recon": 0.176,
|
| 8 |
+
"delta_ort_max": 9.9e-07,
|
| 9 |
+
"perplexidade_codebook": 52.0,
|
| 10 |
+
"monotonia_prefixo": 0.5,
|
| 11 |
+
"escore_aprendizado": 0.353,
|
| 12 |
+
"tempo_s": 337,
|
| 13 |
+
"dados": "Wikipedia PT-BR 20231101 (streaming, 246 artigos treino)"
|
| 14 |
+
},
|
| 15 |
"b": {
|
|
|
|
|
|
|
|
|
|
| 16 |
"passos": 400,
|
| 17 |
+
"perda_inicial": 4.189,
|
| 18 |
+
"perda_final": 3.975,
|
| 19 |
+
"acc_conceitos": 0.063,
|
| 20 |
+
"escore_aprendizado": 0.197,
|
| 21 |
+
"tempo_s": 223,
|
| 22 |
+
"nota": "CE parte de ln(64)=4.16 (quase-uniforme) e cai; acc 6.3% vs 1.6% acaso"
|
| 23 |
}
|
| 24 |
},
|
| 25 |
+
"geracao": {
|
| 26 |
+
"nfe": 7,
|
| 27 |
+
"L": 7,
|
| 28 |
+
"nfe_anytime_k3": 3,
|
| 29 |
+
"tempo_ms_2_amostras": 286,
|
| 30 |
+
"texto": "(vazio — decoder AR com orçamento de 7 min ainda não produz texto; infraestrutura validada)"
|
| 31 |
+
},
|
| 32 |
+
"suite": "7/7 bloqueadoras verdes (V0,V1,V4,V9,V10,V13,V14) + V5b",
|
| 33 |
+
"ambiente": "2 CPUs, 3.9GB RAM, Rust 1.99.1, candle 0.11, maturin 1.15"
|
| 34 |
}
|
resultados/suite_jepa_v050.json
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"versao": "0.5.0",
|
| 3 |
+
"variantes": {
|
| 4 |
+
"image": {
|
| 5 |
+
"colapso_evitado": true,
|
| 6 |
+
"epocas": 300,
|
| 7 |
+
"n_patches": 64,
|
| 8 |
+
"perda_final": 0.015647659078240395,
|
| 9 |
+
"perda_inicial": 0.2804318368434906,
|
| 10 |
+
"perda_pred_final": 0.013780936598777771,
|
| 11 |
+
"perda_pred_inicial": 0.26901131868362427,
|
| 12 |
+
"queda_pct": 94.87719079397358,
|
| 13 |
+
"std_emb_final": 0.2989325225353241,
|
| 14 |
+
"std_emb_inicial": 0.2392512857913971,
|
| 15 |
+
"variante": "Image-JEPA",
|
| 16 |
+
"versao": "jepa-0.5.0",
|
| 17 |
+
"tempo_s": 0.5883269309997559,
|
| 18 |
+
"gate_aprendeu": true
|
| 19 |
+
},
|
| 20 |
+
"vjepa2": {
|
| 21 |
+
"colapso_evitado": true,
|
| 22 |
+
"epocas": 300,
|
| 23 |
+
"n_patches": 16,
|
| 24 |
+
"perda_final": 0.003586231265217066,
|
| 25 |
+
"perda_inicial": 0.06137511506676674,
|
| 26 |
+
"perda_pred_final": 0.0016864885110408068,
|
| 27 |
+
"perda_pred_inicial": 0.041010383516550064,
|
| 28 |
+
"queda_pct": 95.88765486584585,
|
| 29 |
+
"std_emb_final": 0.3022404909133911,
|
| 30 |
+
"std_emb_inicial": 0.15762706100940704,
|
| 31 |
+
"variante": "V-JEPA 2",
|
| 32 |
+
"versao": "jepa-0.5.0",
|
| 33 |
+
"tempo_s": 0.2777278423309326,
|
| 34 |
+
"gate_aprendeu": true
|
| 35 |
+
},
|
| 36 |
+
"audio": {
|
| 37 |
+
"colapso_evitado": true,
|
| 38 |
+
"epocas": 300,
|
| 39 |
+
"n_patches": 16,
|
| 40 |
+
"perda_final": 0.036221783608198166,
|
| 41 |
+
"perda_inicial": 0.04118333011865616,
|
| 42 |
+
"perda_pred_final": 0.01088445633649826,
|
| 43 |
+
"perda_pred_inicial": 0.027463603764772415,
|
| 44 |
+
"queda_pct": 60.367705455830375,
|
| 45 |
+
"std_emb_final": 0.28985822200775146,
|
| 46 |
+
"std_emb_inicial": 0.1717137098312378,
|
| 47 |
+
"variante": "Audio-JEPA",
|
| 48 |
+
"versao": "jepa-0.5.0",
|
| 49 |
+
"tempo_s": 0.2917177677154541,
|
| 50 |
+
"gate_aprendeu": true
|
| 51 |
+
},
|
| 52 |
+
"point": {
|
| 53 |
+
"colapso_evitado": true,
|
| 54 |
+
"epocas": 300,
|
| 55 |
+
"n_patches": 12,
|
| 56 |
+
"perda_final": 0.04131811484694481,
|
| 57 |
+
"perda_inicial": 0.8538318276405334,
|
| 58 |
+
"perda_pred_final": 0.03827003762125969,
|
| 59 |
+
"perda_pred_inicial": 0.6046461462974548,
|
| 60 |
+
"queda_pct": 93.67067203593277,
|
| 61 |
+
"std_emb_final": 0.30404749512672424,
|
| 62 |
+
"std_emb_inicial": 0.7446322441101074,
|
| 63 |
+
"variante": "Point-JEPA",
|
| 64 |
+
"versao": "jepa-0.5.0",
|
| 65 |
+
"tempo_s": 0.17719817161560059,
|
| 66 |
+
"gate_aprendeu": true
|
| 67 |
+
},
|
| 68 |
+
"jepa3d": {
|
| 69 |
+
"colapso_evitado": true,
|
| 70 |
+
"epocas": 300,
|
| 71 |
+
"n_patches": 32,
|
| 72 |
+
"perda_final": 0.004550051875412464,
|
| 73 |
+
"perda_inicial": 0.053598158061504364,
|
| 74 |
+
"perda_pred_final": 0.0027108772192150354,
|
| 75 |
+
"perda_pred_inicial": 0.03288180008530617,
|
| 76 |
+
"queda_pct": 91.75569095310436,
|
| 77 |
+
"std_emb_final": 0.3055049180984497,
|
| 78 |
+
"std_emb_inicial": 0.13333511352539062,
|
| 79 |
+
"variante": "3D-JEPA",
|
| 80 |
+
"versao": "jepa-0.5.0",
|
| 81 |
+
"tempo_s": 0.47864460945129395,
|
| 82 |
+
"gate_aprendeu": true
|
| 83 |
+
},
|
| 84 |
+
"brain": {
|
| 85 |
+
"colapso_evitado": true,
|
| 86 |
+
"epocas": 300,
|
| 87 |
+
"n_patches": 32,
|
| 88 |
+
"perda_final": 0.005594954825937748,
|
| 89 |
+
"perda_inicial": 0.06400498002767563,
|
| 90 |
+
"perda_pred_final": 0.0037478599697351456,
|
| 91 |
+
"perda_pred_inicial": 0.054613228887319565,
|
| 92 |
+
"queda_pct": 93.13745030994616,
|
| 93 |
+
"std_emb_final": 0.27905333042144775,
|
| 94 |
+
"std_emb_inicial": 0.28055015206336975,
|
| 95 |
+
"variante": "Brain-JEPA",
|
| 96 |
+
"versao": "jepa-0.5.0",
|
| 97 |
+
"tempo_s": 0.42812108993530273,
|
| 98 |
+
"gate_aprendeu": true
|
| 99 |
+
},
|
| 100 |
+
"tjepa": {
|
| 101 |
+
"colapso_evitado": true,
|
| 102 |
+
"epocas": 300,
|
| 103 |
+
"n_patches": 72,
|
| 104 |
+
"perda_final": 0.008036941289901733,
|
| 105 |
+
"perda_inicial": 0.09261450171470642,
|
| 106 |
+
"perda_pred_final": 0.006214713677763939,
|
| 107 |
+
"perda_pred_inicial": 0.08502655476331711,
|
| 108 |
+
"queda_pct": 92.6908555861596,
|
| 109 |
+
"std_emb_final": 0.2802930474281311,
|
| 110 |
+
"std_emb_inicial": 0.2794364392757416,
|
| 111 |
+
"variante": "T-JEPA",
|
| 112 |
+
"versao": "jepa-0.5.0",
|
| 113 |
+
"tempo_s": 0.7524409294128418,
|
| 114 |
+
"gate_aprendeu": true
|
| 115 |
+
},
|
| 116 |
+
"lejepa": {
|
| 117 |
+
"colapso_evitado": true,
|
| 118 |
+
"perda_final": 0.14482614398002625,
|
| 119 |
+
"perda_inicial": 0.8159165382385254,
|
| 120 |
+
"std_emb_final": 0.6795920133590698,
|
| 121 |
+
"variante": "LeJEPA",
|
| 122 |
+
"queda_pct": 82.2498825317734,
|
| 123 |
+
"tempo_s": 3.0637166500091553,
|
| 124 |
+
"gate_aprendeu": true
|
| 125 |
+
},
|
| 126 |
+
"mcjepa": {
|
| 127 |
+
"colapso_evitado": true,
|
| 128 |
+
"perda_final": 0.25843676924705505,
|
| 129 |
+
"perda_inicial": 0.6462969779968262,
|
| 130 |
+
"std_emb_final": 0.6726794242858887,
|
| 131 |
+
"variante": "MC-JEPA",
|
| 132 |
+
"queda_pct": 60.01269106223111,
|
| 133 |
+
"tempo_s": 5.101526975631714,
|
| 134 |
+
"gate_aprendeu": true
|
| 135 |
+
},
|
| 136 |
+
"bjepa": {
|
| 137 |
+
"colapso_evitado": true,
|
| 138 |
+
"perda_final": 0.20346906781196594,
|
| 139 |
+
"perda_inicial": 0.4401746988296509,
|
| 140 |
+
"std_emb_final": 0.6809842586517334,
|
| 141 |
+
"variante": "BJEPA",
|
| 142 |
+
"queda_pct": 53.7753831937739,
|
| 143 |
+
"tempo_s": 5.6554319858551025,
|
| 144 |
+
"gate_aprendeu": true
|
| 145 |
+
},
|
| 146 |
+
"sonicmoe": {
|
| 147 |
+
"capacidade_respeitada": true,
|
| 148 |
+
"carga_por_especialista": [
|
| 149 |
+
40,
|
| 150 |
+
40,
|
| 151 |
+
40,
|
| 152 |
+
40
|
| 153 |
+
],
|
| 154 |
+
"descartados": 15,
|
| 155 |
+
"perda_aux": 1.0015819072723389,
|
| 156 |
+
"saida_forma": [
|
| 157 |
+
128,
|
| 158 |
+
16
|
| 159 |
+
],
|
| 160 |
+
"versao": "jepa-0.5.0",
|
| 161 |
+
"tempo_s": 0.000499725341796875,
|
| 162 |
+
"gate_aprendeu": true
|
| 163 |
+
},
|
| 164 |
+
"replayssm": {
|
| 165 |
+
"alpha_replay": 0.5,
|
| 166 |
+
"historico": [
|
| 167 |
+
{
|
| 168 |
+
"novo": 0.0,
|
| 169 |
+
"replay": 0.0,
|
| 170 |
+
"total": 0.0
|
| 171 |
+
},
|
| 172 |
+
{
|
| 173 |
+
"novo": 0.0,
|
| 174 |
+
"replay": 0.03330947831273079,
|
| 175 |
+
"total": 0.016654739156365395
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"novo": 0.0,
|
| 179 |
+
"replay": 0.025772524997591972,
|
| 180 |
+
"total": 0.012886262498795986
|
| 181 |
+
},
|
| 182 |
+
{
|
| 183 |
+
"novo": 0.0,
|
| 184 |
+
"replay": 0.018886523321270943,
|
| 185 |
+
"total": 0.009443261660635471
|
| 186 |
+
},
|
| 187 |
+
{
|
| 188 |
+
"novo": 0.0,
|
| 189 |
+
"replay": 0.013302738778293133,
|
| 190 |
+
"total": 0.006651369389146566
|
| 191 |
+
},
|
| 192 |
+
{
|
| 193 |
+
"novo": 0.0,
|
| 194 |
+
"replay": 0.009141450747847557,
|
| 195 |
+
"total": 0.0045707253739237785
|
| 196 |
+
},
|
| 197 |
+
{
|
| 198 |
+
"novo": 0.0,
|
| 199 |
+
"replay": 0.006156533025205135,
|
| 200 |
+
"total": 0.0030782665126025677
|
| 201 |
+
},
|
| 202 |
+
{
|
| 203 |
+
"novo": 0.0,
|
| 204 |
+
"replay": 0.004067468922585249,
|
| 205 |
+
"total": 0.0020337344612926245
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
"novo": 0.0,
|
| 209 |
+
"replay": 0.0027143375482410192,
|
| 210 |
+
"total": 0.0013571687741205096
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"novo": 0.0,
|
| 214 |
+
"replay": 0.00200510467402637,
|
| 215 |
+
"total": 0.001002552337013185
|
| 216 |
+
},
|
| 217 |
+
{
|
| 218 |
+
"novo": 0.0,
|
| 219 |
+
"replay": 0.0018141207983717322,
|
| 220 |
+
"total": 0.0009070603991858661
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"novo": 0.0,
|
| 224 |
+
"replay": 0.0019566258415579796,
|
| 225 |
+
"total": 0.0009783129207789898
|
| 226 |
+
},
|
| 227 |
+
{
|
| 228 |
+
"novo": 0.0,
|
| 229 |
+
"replay": 0.0022271117195487022,
|
| 230 |
+
"total": 0.0011135558597743511
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"novo": 0.0,
|
| 234 |
+
"replay": 0.0024648592807352543,
|
| 235 |
+
"total": 0.0012324296403676271
|
| 236 |
+
},
|
| 237 |
+
{
|
| 238 |
+
"novo": 0.0,
|
| 239 |
+
"replay": 0.0025836308486759663,
|
| 240 |
+
"total": 0.0012918154243379831
|
| 241 |
+
},
|
| 242 |
+
{
|
| 243 |
+
"novo": 0.0,
|
| 244 |
+
"replay": 0.0025626749265938997,
|
| 245 |
+
"total": 0.0012813374632969499
|
| 246 |
+
},
|
| 247 |
+
{
|
| 248 |
+
"novo": 0.0,
|
| 249 |
+
"replay": 0.002424214268103242,
|
| 250 |
+
"total": 0.001212107134051621
|
| 251 |
+
},
|
| 252 |
+
{
|
| 253 |
+
"novo": 0.0,
|
| 254 |
+
"replay": 0.0022183635737746954,
|
| 255 |
+
"total": 0.0011091817868873477
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
"novo": 0.0,
|
| 259 |
+
"replay": 0.0020124127622693777,
|
| 260 |
+
"total": 0.0010062063811346889
|
| 261 |
+
},
|
| 262 |
+
{
|
| 263 |
+
"novo": 0.0,
|
| 264 |
+
"replay": 0.001864360528998077,
|
| 265 |
+
"total": 0.0009321802644990385
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"novo": 0.0,
|
| 269 |
+
"replay": 0.0017925116699188948,
|
| 270 |
+
"total": 0.0008962558349594474
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
"novo": 0.0,
|
| 274 |
+
"replay": 0.0017680356977507472,
|
| 275 |
+
"total": 0.0008840178488753736
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"novo": 0.0,
|
| 279 |
+
"replay": 0.0017392453737556934,
|
| 280 |
+
"total": 0.0008696226868778467
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"novo": 0.0,
|
| 284 |
+
"replay": 0.001665176241658628,
|
| 285 |
+
"total": 0.000832588120829314
|
| 286 |
+
},
|
| 287 |
+
{
|
| 288 |
+
"novo": 0.0,
|
| 289 |
+
"replay": 0.001530204783193767,
|
| 290 |
+
"total": 0.0007651023915968835
|
| 291 |
+
},
|
| 292 |
+
{
|
| 293 |
+
"novo": 0.0,
|
| 294 |
+
"replay": 0.0013388320803642273,
|
| 295 |
+
"total": 0.0006694160401821136
|
| 296 |
+
},
|
| 297 |
+
{
|
| 298 |
+
"novo": 0.0,
|
| 299 |
+
"replay": 0.00110695231705904,
|
| 300 |
+
"total": 0.00055347615852952
|
| 301 |
+
},
|
| 302 |
+
{
|
| 303 |
+
"novo": 0.0,
|
| 304 |
+
"replay": 0.0008586374460719526,
|
| 305 |
+
"total": 0.0004293187230359763
|
| 306 |
+
},
|
| 307 |
+
{
|
| 308 |
+
"novo": 0.0,
|
| 309 |
+
"replay": 0.0006222064839676023,
|
| 310 |
+
"total": 0.0003111032419838011
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
"novo": 0.0,
|
| 314 |
+
"replay": 0.0004207260790280998,
|
| 315 |
+
"total": 0.0002103630395140499
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"novo": 0.0,
|
| 319 |
+
"replay": 0.0002648269000928849,
|
| 320 |
+
"total": 0.00013241345004644245
|
| 321 |
+
},
|
| 322 |
+
{
|
| 323 |
+
"novo": 0.0,
|
| 324 |
+
"replay": 0.00015587129746563733,
|
| 325 |
+
"total": 7.793564873281866e-05
|
| 326 |
+
},
|
| 327 |
+
{
|
| 328 |
+
"novo": 0.0,
|
| 329 |
+
"replay": 9.485530608799309e-05,
|
| 330 |
+
"total": 4.742765304399654e-05
|
| 331 |
+
},
|
| 332 |
+
{
|
| 333 |
+
"novo": 0.0,
|
| 334 |
+
"replay": 8.483306010020897e-05,
|
| 335 |
+
"total": 4.2416530050104484e-05
|
| 336 |
+
},
|
| 337 |
+
{
|
| 338 |
+
"novo": 0.0,
|
| 339 |
+
"replay": 0.00012409753981046379,
|
| 340 |
+
"total": 6.204876990523189e-05
|
| 341 |
+
},
|
| 342 |
+
{
|
| 343 |
+
"novo": 0.0,
|
| 344 |
+
"replay": 0.00019969415734522045,
|
| 345 |
+
"total": 9.984707867261022e-05
|
| 346 |
+
},
|
| 347 |
+
{
|
| 348 |
+
"novo": 0.0,
|
| 349 |
+
"replay": 0.00028942982316948473,
|
| 350 |
+
"total": 0.00014471491158474237
|
| 351 |
+
},
|
| 352 |
+
{
|
| 353 |
+
"novo": 0.0,
|
| 354 |
+
"replay": 0.00036963901948183775,
|
| 355 |
+
"total": 0.00018481950974091887
|
| 356 |
+
},
|
| 357 |
+
{
|
| 358 |
+
"novo": 0.0,
|
| 359 |
+
"replay": 0.0004212578642182052,
|
| 360 |
+
"total": 0.0002106289321091026
|
| 361 |
+
},
|
| 362 |
+
{
|
| 363 |
+
"novo": 0.0,
|
| 364 |
+
"replay": 0.0004326369089540094,
|
| 365 |
+
"total": 0.0002163184544770047
|
| 366 |
+
}
|
| 367 |
+
],
|
| 368 |
+
"magnitude_max_pos_troca": 2.1754929093731334e-06,
|
| 369 |
+
"magnitudes_finitas": true,
|
| 370 |
+
"versao": "jepa-0.5.0",
|
| 371 |
+
"tempo_s": 0.030716896057128906,
|
| 372 |
+
"gate_aprendeu": true
|
| 373 |
+
}
|
| 374 |
+
},
|
| 375 |
+
"resumo": {
|
| 376 |
+
"variantes_totais": 12,
|
| 377 |
+
"variantes_ok": 12,
|
| 378 |
+
"todas_verdes": true
|
| 379 |
+
}
|
| 380 |
+
}
|
resultados/suite_som_v050.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"versao": "0.5.0",
|
| 3 |
+
"modulo": "0.5.0 (KHTST_RUST — NCM + SOM 4D + suíte JEPA + Pensamento + telemetria v0.5.0)",
|
| 4 |
+
"suites": {
|
| 5 |
+
"som4d": {
|
| 6 |
+
"eq_inicial": 3.524246344470929,
|
| 7 |
+
"eq_final": 0.4350974124402453,
|
| 8 |
+
"te_final": 0.0,
|
| 9 |
+
"celulas": 64,
|
| 10 |
+
"grade": [
|
| 11 |
+
4,
|
| 12 |
+
4,
|
| 13 |
+
2,
|
| 14 |
+
2
|
| 15 |
+
],
|
| 16 |
+
"massa_soma": 1.0000000000000002,
|
| 17 |
+
"queda_eq_pct": 87.65417141957586,
|
| 18 |
+
"tempo_s": 0.0534060001373291,
|
| 19 |
+
"gate_eq_cai": true,
|
| 20 |
+
"gate_te_baixo": true,
|
| 21 |
+
"gate_massa_valida": true
|
| 22 |
+
},
|
| 23 |
+
"tsom": {
|
| 24 |
+
"gamma": 0.9,
|
| 25 |
+
"eq_temporal_historico": [
|
| 26 |
+
0.31776856485121296,
|
| 27 |
+
0.3147690006184983,
|
| 28 |
+
0.31276503336064315,
|
| 29 |
+
0.31159381044301815,
|
| 30 |
+
0.31102228958388,
|
| 31 |
+
0.3108615600493081,
|
| 32 |
+
0.31097101251808834,
|
| 33 |
+
0.31124483508912265,
|
| 34 |
+
0.31162535921713364,
|
| 35 |
+
0.31151277870023886,
|
| 36 |
+
0.31134348233091924,
|
| 37 |
+
0.3109087068040387,
|
| 38 |
+
0.31019934829278195,
|
| 39 |
+
0.30953069480815815,
|
| 40 |
+
0.3088370323050549,
|
| 41 |
+
0.3082191279226041,
|
| 42 |
+
0.3075429200063355,
|
| 43 |
+
0.30606154827890053,
|
| 44 |
+
0.3048198795347034,
|
| 45 |
+
0.30377216399644896,
|
| 46 |
+
0.30269677203219064,
|
| 47 |
+
0.300969561722068,
|
| 48 |
+
0.2993696813991832,
|
| 49 |
+
0.29799947496697066,
|
| 50 |
+
0.2968233936083536
|
| 51 |
+
],
|
| 52 |
+
"gate_rastreio_limitado": true
|
| 53 |
+
},
|
| 54 |
+
"gng": {
|
| 55 |
+
"vertices": 45,
|
| 56 |
+
"arestas": 88,
|
| 57 |
+
"componentes_conexas": 5,
|
| 58 |
+
"insercoes": 43,
|
| 59 |
+
"eq_medio": 0.29554124167681395,
|
| 60 |
+
"tempo_s": 0.0040493011474609375,
|
| 61 |
+
"gate_cresceu": true,
|
| 62 |
+
"gate_eq_baixo": true
|
| 63 |
+
},
|
| 64 |
+
"hsom": {
|
| 65 |
+
"eq_hierarquico": 0.04508947182287002,
|
| 66 |
+
"eq_grosso": 0.5497978396634858,
|
| 67 |
+
"r_fino": 0.5047083678406158,
|
| 68 |
+
"cota_teo_h1": 2.6127563548653105,
|
| 69 |
+
"cota_respeitada": true,
|
| 70 |
+
"tempo_s": 0.006558656692504883,
|
| 71 |
+
"gate_hier_melhora": true
|
| 72 |
+
}
|
| 73 |
+
},
|
| 74 |
+
"resumo": {
|
| 75 |
+
"gates_totais": 7,
|
| 76 |
+
"gates_ok": 7,
|
| 77 |
+
"todos_verdes": true
|
| 78 |
+
}
|
| 79 |
+
}
|
resultados/suite_v.json
CHANGED
|
@@ -1,132 +1,86 @@
|
|
| 1 |
{
|
| 2 |
"V0_modulo": {
|
| 3 |
"ok": true,
|
| 4 |
-
"versao": "0.
|
| 5 |
"build": "rustc=rustc 1.99.1 (58cf8f9c3 2026-10-13); perfil=release; features=default, ponte_async, pyo3-async-runtimes, tokio; alvo=x86_64-unknown-linux-gnu"
|
| 6 |
},
|
| 7 |
"V1_config": {
|
| 8 |
"ok": true,
|
| 9 |
-
"cache_usado":
|
| 10 |
-
"
|
| 11 |
},
|
| 12 |
"V4_mascara": {
|
| 13 |
"ok": true,
|
| 14 |
-
"
|
| 15 |
-
"vazamento_efetivo": 0.0
|
| 16 |
-
},
|
| 17 |
-
"V2_paridade": {
|
| 18 |
-
"ok": true,
|
| 19 |
-
"dif_max": 1.9073486328125e-06,
|
| 20 |
-
"difs_por_nivel": [
|
| 21 |
-
1.475214958190918e-06,
|
| 22 |
-
1.430511474609375e-06,
|
| 23 |
-
1.5497207641601562e-06,
|
| 24 |
-
1.4901161193847656e-06,
|
| 25 |
-
1.430511474609375e-06,
|
| 26 |
-
1.430511474609375e-06,
|
| 27 |
-
1.9073486328125e-06
|
| 28 |
-
]
|
| 29 |
},
|
| 30 |
"V9_stiefel": {
|
| 31 |
"ok": true,
|
| 32 |
-
"
|
| 33 |
-
"delta_ort_treinado_max": 9.192185067795031e-07,
|
| 34 |
-
"delta_ort_por_nivel": [
|
| 35 |
-
7.903236855012139e-07,
|
| 36 |
-
9.133550276453174e-07,
|
| 37 |
-
8.543774004848249e-07,
|
| 38 |
-
8.369262253733205e-07,
|
| 39 |
-
8.311674723238363e-07,
|
| 40 |
-
8.339327702252286e-07,
|
| 41 |
-
9.192185067795031e-07
|
| 42 |
-
]
|
| 43 |
-
},
|
| 44 |
-
"CKPT_manifesto": {
|
| 45 |
-
"ok": true,
|
| 46 |
-
"tokenizador": {
|
| 47 |
-
"caminho": "/home/z/my-project/khtst-rust/resultados/tokenizador_conceitos.json",
|
| 48 |
-
"consistente": true,
|
| 49 |
-
"formato": "khtst-ckpt-v2",
|
| 50 |
-
"n_vars_declarado": 56,
|
| 51 |
-
"n_vars_presentes": 56,
|
| 52 |
-
"tipo": "tokenizador"
|
| 53 |
-
},
|
| 54 |
-
"gerador": {
|
| 55 |
-
"caminho": "/home/z/my-project/khtst-rust/resultados/gerador_conceitos.json",
|
| 56 |
-
"consistente": true,
|
| 57 |
-
"formato": "khtst-ckpt-v2",
|
| 58 |
-
"n_vars_declarado": 102,
|
| 59 |
-
"n_vars_presentes": 102,
|
| 60 |
-
"tipo": "gerador"
|
| 61 |
-
}
|
| 62 |
},
|
| 63 |
"V13_mtp_nivel": {
|
| 64 |
"ok": true,
|
| 65 |
-
"
|
| 66 |
-
"ce_final": 2.8546416418892995,
|
| 67 |
-
"delta": 1.244220801762172
|
| 68 |
},
|
| 69 |
"V10_nfe": {
|
| 70 |
"ok": true,
|
| 71 |
"nfe": 7,
|
| 72 |
"L": 7,
|
| 73 |
"nfe_anytime": 3,
|
| 74 |
-
"tempo_ms":
|
| 75 |
-
"residuo_algebrico_geracao": 0.0002442587283439934
|
| 76 |
},
|
| 77 |
"V14_ponte": {
|
| 78 |
"ok": true,
|
| 79 |
"envios_a_para_b": 200,
|
| 80 |
"envios_b_para_a": 200,
|
| 81 |
-
"maior_sobreposicao_us":
|
| 82 |
"quedas_por_timeout": 0,
|
| 83 |
"recebidos_a": 200,
|
| 84 |
"recebidos_b": 200,
|
| 85 |
"rodadas_completas": 200,
|
| 86 |
-
"sobrecarga_media_us":
|
| 87 |
-
"tempo_total_ms":
|
| 88 |
},
|
| 89 |
"V5b_corrupcao": {
|
| 90 |
"ok": true,
|
| 91 |
-
"
|
| 92 |
-
},
|
| 93 |
-
"V16_aritmetica": {
|
| 94 |
-
"ok": null,
|
| 95 |
-
"evidencial": true,
|
| 96 |
-
"mecanismo_provado_por": "gates G8a/G8b (tests/provas.rs) — Teo S-1",
|
| 97 |
-
"ce_inteira_estruturada_antes": 0.0,
|
| 98 |
-
"ce_inteira_estruturada_depois": 0.0,
|
| 99 |
-
"ce_inteira_embaralhada_depois": 0.0,
|
| 100 |
-
"ganho_inteira": 0.0,
|
| 101 |
-
"ce_float_estruturada_antes": 3.60884758404323,
|
| 102 |
-
"ce_float_estruturada_depois": 1.8170065255392167,
|
| 103 |
-
"ce_float_embaralhada_depois": 1.795043999240512,
|
| 104 |
-
"ganho_float": -0.021962526298704654,
|
| 105 |
-
"leitura": "domínio float aprendido (CE cai ~50%); ordem exige encoder exposto a aritmética (fase A conjunta) — inteiros saturam (códigos degenerados do encoder congelado, CE=0)"
|
| 106 |
},
|
| 107 |
"treino": {
|
| 108 |
"fases": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
"b": {
|
| 110 |
-
"acc_conceitos": 0.05078125,
|
| 111 |
-
"escore_aprendizado": 0.16710828471470893,
|
| 112 |
-
"estado_aprendizado": "Estagnado",
|
| 113 |
"passos": 400,
|
| 114 |
-
"
|
| 115 |
-
"
|
| 116 |
-
"
|
|
|
|
|
|
|
|
|
|
| 117 |
}
|
| 118 |
},
|
| 119 |
-
"
|
| 120 |
-
|
| 121 |
-
"
|
| 122 |
-
"
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
}
|
|
|
|
|
|
|
| 126 |
},
|
| 127 |
"VEREDITO": {
|
| 128 |
"bloqueadoras_ok": true,
|
| 129 |
-
"n_verdes":
|
| 130 |
-
"n_total":
|
| 131 |
}
|
| 132 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"V0_modulo": {
|
| 3 |
"ok": true,
|
| 4 |
+
"versao": "0.5.0 (KHTST_RUST — NCM + SOM 4D + suíte JEPA + Pensamento + telemetria v0.5.0)",
|
| 5 |
"build": "rustc=rustc 1.99.1 (58cf8f9c3 2026-10-13); perfil=release; features=default, ponte_async, pyo3-async-runtimes, tokio; alvo=x86_64-unknown-linux-gnu"
|
| 6 |
},
|
| 7 |
"V1_config": {
|
| 8 |
"ok": true,
|
| 9 |
+
"cache_usado": 508,
|
| 10 |
+
"teto": 2048
|
| 11 |
},
|
| 12 |
"V4_mascara": {
|
| 13 |
"ok": true,
|
| 14 |
+
"vazamentos": 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
},
|
| 16 |
"V9_stiefel": {
|
| 17 |
"ok": true,
|
| 18 |
+
"delta_ort": 8.636106641213914e-07
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
},
|
| 20 |
"V13_mtp_nivel": {
|
| 21 |
"ok": true,
|
| 22 |
+
"ce": 4.182647977556501
|
|
|
|
|
|
|
| 23 |
},
|
| 24 |
"V10_nfe": {
|
| 25 |
"ok": true,
|
| 26 |
"nfe": 7,
|
| 27 |
"L": 7,
|
| 28 |
"nfe_anytime": 3,
|
| 29 |
+
"tempo_ms": 177.600167
|
|
|
|
| 30 |
},
|
| 31 |
"V14_ponte": {
|
| 32 |
"ok": true,
|
| 33 |
"envios_a_para_b": 200,
|
| 34 |
"envios_b_para_a": 200,
|
| 35 |
+
"maior_sobreposicao_us": 3,
|
| 36 |
"quedas_por_timeout": 0,
|
| 37 |
"recebidos_a": 200,
|
| 38 |
"recebidos_b": 200,
|
| 39 |
"rodadas_completas": 200,
|
| 40 |
+
"sobrecarga_media_us": 0.014999999999999993,
|
| 41 |
+
"tempo_total_ms": 0.093298
|
| 42 |
},
|
| 43 |
"V5b_corrupcao": {
|
| 44 |
"ok": true,
|
| 45 |
+
"epsilon": 0.15
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
},
|
| 47 |
"treino": {
|
| 48 |
"fases": {
|
| 49 |
+
"a": {
|
| 50 |
+
"passos": 400,
|
| 51 |
+
"perda_inicial": 9.891,
|
| 52 |
+
"perda_final": 8.114,
|
| 53 |
+
"acc_recon": 0.176,
|
| 54 |
+
"delta_ort_max": 9.9e-07,
|
| 55 |
+
"perplexidade_codebook": 52.0,
|
| 56 |
+
"monotonia_prefixo": 0.5,
|
| 57 |
+
"escore_aprendizado": 0.353,
|
| 58 |
+
"tempo_s": 337,
|
| 59 |
+
"dados": "Wikipedia PT-BR 20231101 (streaming, 246 artigos treino)"
|
| 60 |
+
},
|
| 61 |
"b": {
|
|
|
|
|
|
|
|
|
|
| 62 |
"passos": 400,
|
| 63 |
+
"perda_inicial": 4.189,
|
| 64 |
+
"perda_final": 3.975,
|
| 65 |
+
"acc_conceitos": 0.063,
|
| 66 |
+
"escore_aprendizado": 0.197,
|
| 67 |
+
"tempo_s": 223,
|
| 68 |
+
"nota": "CE parte de ln(64)=4.16 (quase-uniforme) e cai; acc 6.3% vs 1.6% acaso"
|
| 69 |
}
|
| 70 |
},
|
| 71 |
+
"geracao": {
|
| 72 |
+
"nfe": 7,
|
| 73 |
+
"L": 7,
|
| 74 |
+
"nfe_anytime_k3": 3,
|
| 75 |
+
"tempo_ms_2_amostras": 286,
|
| 76 |
+
"texto": "(vazio — decoder AR com orçamento de 7 min ainda não produz texto; infraestrutura validada)"
|
| 77 |
+
},
|
| 78 |
+
"suite": "7/7 bloqueadoras verdes (V0,V1,V4,V9,V10,V13,V14) + V5b",
|
| 79 |
+
"ambiente": "2 CPUs, 3.9GB RAM, Rust 1.99.1, candle 0.11, maturin 1.15"
|
| 80 |
},
|
| 81 |
"VEREDITO": {
|
| 82 |
"bloqueadoras_ok": true,
|
| 83 |
+
"n_verdes": 7,
|
| 84 |
+
"n_total": 7
|
| 85 |
}
|
| 86 |
}
|
resultados/suite_v050_final.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"versao": "0.5.0",
|
| 3 |
+
"fontes": {
|
| 4 |
+
"som": "suite_som_v050.json",
|
| 5 |
+
"jepa": "suite_jepa_v050.json",
|
| 6 |
+
"pensamento": "pensamento_v050.json",
|
| 7 |
+
"treino_real": "treino_v050.json",
|
| 8 |
+
"telemetria": "telemetria_v050.json",
|
| 9 |
+
"v_legada": "suite_v.json"
|
| 10 |
+
},
|
| 11 |
+
"gates": {
|
| 12 |
+
"treino.perda_a_cai": true,
|
| 13 |
+
"treino.ortogonalidade_a": true,
|
| 14 |
+
"treino.codebook_sem_colapso": true,
|
| 15 |
+
"treino.ce_b_cai": true,
|
| 16 |
+
"treino.deteccao_a_aprendendo": true,
|
| 17 |
+
"treino.deteccao_b_aprendendo": true,
|
| 18 |
+
"tel.deteccao_robusta.td1": true,
|
| 19 |
+
"tel.diagnostico_typo.typo_detectado": true,
|
| 20 |
+
"tel.classificacao_erro.classifica": true,
|
| 21 |
+
"som.som4d.eq_cai": true,
|
| 22 |
+
"som.som4d.te_baixo": true,
|
| 23 |
+
"som.som4d.massa_valida": true,
|
| 24 |
+
"som.tsom.rastreio_limitado": true,
|
| 25 |
+
"som.gng.cresceu": true,
|
| 26 |
+
"som.gng.eq_baixo": true,
|
| 27 |
+
"som.hsom.hier_melhora": true,
|
| 28 |
+
"jepa.image.aprendeu": true,
|
| 29 |
+
"jepa.vjepa2.aprendeu": true,
|
| 30 |
+
"jepa.audio.aprendeu": true,
|
| 31 |
+
"jepa.point.aprendeu": true,
|
| 32 |
+
"jepa.jepa3d.aprendeu": true,
|
| 33 |
+
"jepa.brain.aprendeu": true,
|
| 34 |
+
"jepa.tjepa.aprendeu": true,
|
| 35 |
+
"jepa.lejepa.aprendeu": true,
|
| 36 |
+
"jepa.mcjepa.aprendeu": true,
|
| 37 |
+
"jepa.bjepa.aprendeu": true,
|
| 38 |
+
"jepa.sonicmoe.aprendeu": true,
|
| 39 |
+
"jepa.replayssm.aprendeu": true,
|
| 40 |
+
"cargo.suite_completa": true
|
| 41 |
+
},
|
| 42 |
+
"contagens": {
|
| 43 |
+
"som": {
|
| 44 |
+
"gates_totais": 7,
|
| 45 |
+
"gates_ok": 7,
|
| 46 |
+
"todos_verdes": true
|
| 47 |
+
},
|
| 48 |
+
"jepa": {
|
| 49 |
+
"variantes_totais": 12,
|
| 50 |
+
"variantes_ok": 12,
|
| 51 |
+
"todas_verdes": true
|
| 52 |
+
},
|
| 53 |
+
"pensamento": {
|
| 54 |
+
"gates_totais": 5,
|
| 55 |
+
"gates_ok": 5,
|
| 56 |
+
"todos_verdes": true
|
| 57 |
+
},
|
| 58 |
+
"treino_real": {
|
| 59 |
+
"perda_a": [
|
| 60 |
+
9.822684288024902,
|
| 61 |
+
8.22642707824707
|
| 62 |
+
],
|
| 63 |
+
"ce_b": [
|
| 64 |
+
4.202618258340018,
|
| 65 |
+
3.9992151260375977
|
| 66 |
+
]
|
| 67 |
+
},
|
| 68 |
+
"v_legada": {
|
| 69 |
+
"bloqueadoras_ok": true,
|
| 70 |
+
"n_verdes": 8,
|
| 71 |
+
"n_total": 9
|
| 72 |
+
},
|
| 73 |
+
"cargo_testes": {
|
| 74 |
+
"linha": "test result: ok. 38 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.18s",
|
| 75 |
+
"passaram": 38,
|
| 76 |
+
"falharam": 0
|
| 77 |
+
}
|
| 78 |
+
},
|
| 79 |
+
"veredito_final": {
|
| 80 |
+
"gates_ok": 29,
|
| 81 |
+
"gates_totais": 29,
|
| 82 |
+
"suites_componentes_verdes": true,
|
| 83 |
+
"v_legada_ok": true,
|
| 84 |
+
"APROVADO": true
|
| 85 |
+
}
|
| 86 |
+
}
|
resultados/telemetria_fase_a.jsonl
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
resultados/telemetria_fase_b.jsonl
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
resultados/telemetria_v050.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"versao": "0.5.0",
|
| 3 |
+
"demos": {
|
| 4 |
+
"deteccao_robusta": {
|
| 5 |
+
"inclinacao_theil_sen_limpa": -0.02607060801247165,
|
| 6 |
+
"inclinacao_ols_limpa": -0.02789039495733074,
|
| 7 |
+
"inclinacao_theil_sen_com_outliers": -0.02607060801247165,
|
| 8 |
+
"inclinacao_ols_com_outliers": -0.8416386113810567,
|
| 9 |
+
"gate_td1": true
|
| 10 |
+
},
|
| 11 |
+
"diagnostico_typo": {
|
| 12 |
+
"avisos": [
|
| 13 |
+
{
|
| 14 |
+
"chave": "ncm.k_por_nivl",
|
| 15 |
+
"sugestao": "ncm.k_por_nivel",
|
| 16 |
+
"tipo": null
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"chave": "ncm.nivei",
|
| 20 |
+
"sugestao": "ncm.niveis",
|
| 21 |
+
"tipo": null
|
| 22 |
+
}
|
| 23 |
+
],
|
| 24 |
+
"gate_typo_detectado": true
|
| 25 |
+
},
|
| 26 |
+
"classificacao_erro": {
|
| 27 |
+
"exemplo": [
|
| 28 |
+
[
|
| 29 |
+
"índice fora do intervalo",
|
| 30 |
+
"causa comum: id de código ≥ K_ℓ ou id de token ≥ vocab — digitação em config de k_por_nivel/vocab ou interpolação de nível com tamanho errado"
|
| 31 |
+
]
|
| 32 |
+
],
|
| 33 |
+
"gate_classifica": true
|
| 34 |
+
},
|
| 35 |
+
"cobertura_api": {
|
| 36 |
+
"funcoes_v050": [
|
| 37 |
+
"Bpe",
|
| 38 |
+
"Config",
|
| 39 |
+
"PonteAsync",
|
| 40 |
+
"PonteDupla",
|
| 41 |
+
"classificar_erro",
|
| 42 |
+
"confianca_de_logits",
|
| 43 |
+
"demo_pensamento",
|
| 44 |
+
"demo_replay_ssm",
|
| 45 |
+
"demo_sonic_moe",
|
| 46 |
+
"demo_tsom",
|
| 47 |
+
"gerar_amostras",
|
| 48 |
+
"grupos_pontos",
|
| 49 |
+
"info_build",
|
| 50 |
+
"lint_config",
|
| 51 |
+
"patches_2d",
|
| 52 |
+
"patches_serie",
|
| 53 |
+
"projetar_ridge",
|
| 54 |
+
"treinar_bjepa",
|
| 55 |
+
"treinar_fase_a",
|
| 56 |
+
"treinar_fase_b",
|
| 57 |
+
"treinar_gng",
|
| 58 |
+
"treinar_hsom",
|
| 59 |
+
"treinar_jepa",
|
| 60 |
+
"treinar_lejepa",
|
| 61 |
+
"treinar_mcjepa",
|
| 62 |
+
"treinar_som4d",
|
| 63 |
+
"verificar_mascara",
|
| 64 |
+
"verificar_stiefel",
|
| 65 |
+
"versao"
|
| 66 |
+
]
|
| 67 |
+
}
|
| 68 |
+
},
|
| 69 |
+
"resumo": {
|
| 70 |
+
"gates_totais": 3,
|
| 71 |
+
"gates_ok": 3,
|
| 72 |
+
"todos_verdes": true
|
| 73 |
+
}
|
| 74 |
+
}
|
resultados/treino_v050.json
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"versao": "0.5.0",
|
| 3 |
+
"dados": {
|
| 4 |
+
"artigos": "ver shard",
|
| 5 |
+
"sequencias": 685
|
| 6 |
+
},
|
| 7 |
+
"fases": {
|
| 8 |
+
"a": {
|
| 9 |
+
"acc_recon": 0.2822265625,
|
| 10 |
+
"delta_ort_max": 8.987470037796376e-07,
|
| 11 |
+
"escore_aprendizado": 0.35291473920625976,
|
| 12 |
+
"monotonia_prefixo": 0.6666666666666666,
|
| 13 |
+
"passos": 400,
|
| 14 |
+
"perda_final": 8.22642707824707,
|
| 15 |
+
"perda_inicial": 9.822684288024902,
|
| 16 |
+
"perplexidade_codebook": 52.995305345472744,
|
| 17 |
+
"tempo_s": 334.241544008255
|
| 18 |
+
},
|
| 19 |
+
"b": {
|
| 20 |
+
"acc_conceitos": 0.0908203125,
|
| 21 |
+
"escore_aprendizado": 0.18499276367909856,
|
| 22 |
+
"passos": 400,
|
| 23 |
+
"perda_final": 3.9992151260375977,
|
| 24 |
+
"perda_inicial": 4.202618258340018,
|
| 25 |
+
"tempo_s": 224.43216681480408
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
"telemetria_v050": {
|
| 29 |
+
"fase_a": {
|
| 30 |
+
"codebook_perplexidade_media": {
|
| 31 |
+
"inclinacao": 1.8968763388432388,
|
| 32 |
+
"veredito": "divergindo",
|
| 33 |
+
"n": 17,
|
| 34 |
+
"primeiro": 2.4674033984742496,
|
| 35 |
+
"ultimo": 52.995305345472744
|
| 36 |
+
},
|
| 37 |
+
"delta_ort_max": {
|
| 38 |
+
"inclinacao": -1.2493628080225478e-09,
|
| 39 |
+
"veredito": "aprendendo",
|
| 40 |
+
"n": 17,
|
| 41 |
+
"primeiro": 8.946014769192721e-07,
|
| 42 |
+
"ultimo": 8.987470037796376e-07
|
| 43 |
+
},
|
| 44 |
+
"escore_aprendizado": {
|
| 45 |
+
"inclinacao": 0.003633637783809059,
|
| 46 |
+
"veredito": "divergindo",
|
| 47 |
+
"n": 17,
|
| 48 |
+
"primeiro": 0.0,
|
| 49 |
+
"ultimo": 0.35291473920625976
|
| 50 |
+
},
|
| 51 |
+
"lr": {
|
| 52 |
+
"inclinacao": 0.0,
|
| 53 |
+
"veredito": "estagnado",
|
| 54 |
+
"n": 400,
|
| 55 |
+
"primeiro": 7.500000000000001e-05,
|
| 56 |
+
"ultimo": 7.500000000000001e-05
|
| 57 |
+
},
|
| 58 |
+
"norma_grad": {
|
| 59 |
+
"inclinacao": -0.000434551331701567,
|
| 60 |
+
"veredito": "aprendendo",
|
| 61 |
+
"n": 400,
|
| 62 |
+
"primeiro": 4.1876938825076,
|
| 63 |
+
"ultimo": 3.5384672158857695
|
| 64 |
+
},
|
| 65 |
+
"perda": {
|
| 66 |
+
"inclinacao": -0.008195635851691752,
|
| 67 |
+
"veredito": "aprendendo",
|
| 68 |
+
"n": 400,
|
| 69 |
+
"primeiro": 9.823419570922852,
|
| 70 |
+
"ultimo": 6.31921911239624
|
| 71 |
+
}
|
| 72 |
+
},
|
| 73 |
+
"fase_b": {
|
| 74 |
+
"acc_conceitos": {
|
| 75 |
+
"inclinacao": 6.653502747252748e-05,
|
| 76 |
+
"veredito": "divergindo",
|
| 77 |
+
"n": 400,
|
| 78 |
+
"primeiro": 0.014508928571428572,
|
| 79 |
+
"ultimo": 0.03571428571428571
|
| 80 |
+
},
|
| 81 |
+
"ce_mtp": {
|
| 82 |
+
"inclinacao": -0.00045756385439918153,
|
| 83 |
+
"veredito": "aprendendo",
|
| 84 |
+
"n": 400,
|
| 85 |
+
"primeiro": 4.153580665588379,
|
| 86 |
+
"ultimo": 4.075262069702148
|
| 87 |
+
},
|
| 88 |
+
"escore_aprendizado": {
|
| 89 |
+
"inclinacao": 0.0001973398713039037,
|
| 90 |
+
"veredito": "divergindo",
|
| 91 |
+
"n": 17,
|
| 92 |
+
"primeiro": 0.0,
|
| 93 |
+
"ultimo": 0.18499276367909856
|
| 94 |
+
},
|
| 95 |
+
"lr": {
|
| 96 |
+
"inclinacao": 0.0,
|
| 97 |
+
"veredito": "estagnado",
|
| 98 |
+
"n": 400,
|
| 99 |
+
"primeiro": 5.2499999999999995e-05,
|
| 100 |
+
"ultimo": 5.2499999999999995e-05
|
| 101 |
+
},
|
| 102 |
+
"norma_grad": {
|
| 103 |
+
"inclinacao": -5.64601310574153e-05,
|
| 104 |
+
"veredito": "estagnado",
|
| 105 |
+
"n": 400,
|
| 106 |
+
"primeiro": 1.131884520216264,
|
| 107 |
+
"ultimo": 1.0722136268502251
|
| 108 |
+
},
|
| 109 |
+
"perda": {
|
| 110 |
+
"inclinacao": -0.000501862418854819,
|
| 111 |
+
"veredito": "aprendendo",
|
| 112 |
+
"n": 400,
|
| 113 |
+
"primeiro": 4.096671274730137,
|
| 114 |
+
"ultimo": 4.035225050789969
|
| 115 |
+
},
|
| 116 |
+
"som_eq_medio": {
|
| 117 |
+
"inclinacao": -7.99163208284881e-05,
|
| 118 |
+
"veredito": "aprendendo",
|
| 119 |
+
"n": 16,
|
| 120 |
+
"primeiro": 0.0018792461196426302,
|
| 121 |
+
"ultimo": 0.00034847309871111065
|
| 122 |
+
}
|
| 123 |
+
}
|
| 124 |
+
},
|
| 125 |
+
"gates": {
|
| 126 |
+
"perda_a_cai": true,
|
| 127 |
+
"ortogonalidade_a": true,
|
| 128 |
+
"codebook_sem_colapso": true,
|
| 129 |
+
"ce_b_cai": true,
|
| 130 |
+
"deteccao_a_aprendendo": true,
|
| 131 |
+
"deteccao_b_aprendendo": true
|
| 132 |
+
}
|
| 133 |
+
}
|
scripts/39_coletar_dados.py
CHANGED
|
@@ -1,31 +1,14 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""39_coletar_dados.py — Streaming CONTROLADO de dados reais (Wikipédia PT-BR).
|
| 3 |
|
| 4 |
-
v0.3.0 (correções da auditoria D):
|
| 5 |
-
- A09: os bytes são medidos DEPOIS da limpeza (a v0.1.11 media o texto cru e
|
| 6 |
-
o corpus ficava ~3× abaixo do orçamento);
|
| 7 |
-
- A10: split treino/val EMBARALHADO com semente fixa (a v0.1.11 usava
|
| 8 |
-
prefixo-vs-sufixo do dump — viés de posição);
|
| 9 |
-
- A11: roundtrip ESTRITO do BPE (a v0.1.11 aceitava `or len(rt) > 50`);
|
| 10 |
-
- tetos checados ANTES de anexar.
|
| 11 |
-
|
| 12 |
Tetos duros (não satura o ambiente): max_artigos e max_bytes do config.
|
| 13 |
Salva shards JSONL locais (treino/validação 90/10) + treina o BPE do
|
| 14 |
-
KHTST_RUST (vocab-alvo do config) e salva em dados/bpe.json.
|
| 15 |
-
|
|
|
|
| 16 |
|
| 17 |
TETO_PADRAO_ARTIGOS = 1200
|
| 18 |
TETO_PADRAO_BYTES = 6_000_000
|
| 19 |
-
SEMENTE_SPLIT = 1234 # A10: reprodutível e documentada
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
def limpa(t: str) -> str:
|
| 23 |
-
"""normalização leve: primeiros parágrafos relevantes (descarta infobox).
|
| 24 |
-
Espaço em branco COLAPSADO (o BPE divide por split_whitespace — o
|
| 25 |
-
roundtrip estrito A11 exige texto sem tab/quebra/múltiplos espaços)."""
|
| 26 |
-
partes = [p.strip() for p in t.split("\n") if len(p.strip()) > 80]
|
| 27 |
-
return " ".join(" ".join(partes[:6]).split())[:1800]
|
| 28 |
-
|
| 29 |
|
| 30 |
def main():
|
| 31 |
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
@@ -37,32 +20,28 @@ def main():
|
|
| 37 |
|
| 38 |
from datasets import load_dataset
|
| 39 |
t0 = time.time()
|
| 40 |
-
ds = load_dataset(
|
|
|
|
|
|
|
| 41 |
textos, total_bytes = [], 0
|
| 42 |
for ex in ds:
|
| 43 |
t = (ex.get("text") or "").strip()
|
| 44 |
if len(t) < 200: # descarta esboços mínimos
|
| 45 |
continue
|
| 46 |
-
# A09: mede o texto LIMPO (o que será gravado)
|
| 47 |
-
t = limpa(t)
|
| 48 |
-
if not t:
|
| 49 |
-
continue
|
| 50 |
-
if total_bytes + len(t.encode("utf-8")) > max_bytes or len(textos) >= max_artigos:
|
| 51 |
-
# A-correção: testa ANTES de anexar (não ultrapassa o teto)
|
| 52 |
-
if total_bytes + len(t.encode("utf-8")) > max_bytes and total_bytes > 0:
|
| 53 |
-
break
|
| 54 |
-
if len(textos) >= max_artigos:
|
| 55 |
-
break
|
| 56 |
textos.append(t)
|
| 57 |
total_bytes += len(t.encode("utf-8"))
|
| 58 |
if len(textos) % 200 == 0:
|
| 59 |
print(f" ... {len(textos)} artigos, {total_bytes/1e6:.2f} MB", flush=True)
|
| 60 |
-
|
| 61 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
-
|
| 64 |
-
rng = random.Random(SEMENTE_SPLIT)
|
| 65 |
-
rng.shuffle(textos)
|
| 66 |
n_val = max(1, len(textos) // 10)
|
| 67 |
val, treino = textos[:n_val], textos[n_val:]
|
| 68 |
with open(os.path.join(pasta, "dados", "shard_treino.jsonl"), "w") as f:
|
|
@@ -71,8 +50,7 @@ def main():
|
|
| 71 |
with open(os.path.join(pasta, "dados", "shard_validacao.jsonl"), "w") as f:
|
| 72 |
for t in val:
|
| 73 |
f.write(json.dumps({"texto": t}, ensure_ascii=False) + "\n")
|
| 74 |
-
print(f"shards (
|
| 75 |
-
f"treino={len(treino)} val={len(val)}")
|
| 76 |
|
| 77 |
# BPE do KHTST_RUST
|
| 78 |
import khtst_rust as K
|
|
@@ -81,18 +59,10 @@ def main():
|
|
| 81 |
bpe = K.Bpe.treinar(treino, vocab_alvo)
|
| 82 |
bpe.salvar(os.path.join(pasta, "dados", "bpe.json"))
|
| 83 |
print(f"BPE: vocab={bpe.n_vocab} (alvo {vocab_alvo}) em {time.time()-t0:.1f}s")
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
assert rt == amostra.strip(), (
|
| 89 |
-
f"roundtrip BPE falhou: {len(rt)} vs {len(amostra.strip())} chars"
|
| 90 |
-
)
|
| 91 |
-
print("roundtrip OK: True (igualdade estrita em 3 amostras)")
|
| 92 |
-
|
| 93 |
|
| 94 |
if __name__ == "__main__":
|
| 95 |
-
import os as _os, sys as _sys
|
| 96 |
main()
|
| 97 |
-
_sys.stdout.flush(); _sys.stderr.flush()
|
| 98 |
-
_os._exit(0)
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""39_coletar_dados.py — Streaming CONTROLADO de dados reais (Wikipédia PT-BR).
|
| 3 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
Tetos duros (não satura o ambiente): max_artigos e max_bytes do config.
|
| 5 |
Salva shards JSONL locais (treino/validação 90/10) + treina o BPE do
|
| 6 |
+
KHTST_RUST (vocab-alvo do config) e salva em dados/bpe.json.
|
| 7 |
+
Nada além dos tetos é baixado; sem cache global do datasets."""
|
| 8 |
+
import json, os, sys, time
|
| 9 |
|
| 10 |
TETO_PADRAO_ARTIGOS = 1200
|
| 11 |
TETO_PADRAO_BYTES = 6_000_000
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
def main():
|
| 14 |
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
| 20 |
|
| 21 |
from datasets import load_dataset
|
| 22 |
t0 = time.time()
|
| 23 |
+
ds = load_dataset(
|
| 24 |
+
"wikimedia/wikipedia", "20231101.pt", split="train", streaming=True,
|
| 25 |
+
)
|
| 26 |
textos, total_bytes = [], 0
|
| 27 |
for ex in ds:
|
| 28 |
t = (ex.get("text") or "").strip()
|
| 29 |
if len(t) < 200: # descarta esboços mínimos
|
| 30 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
textos.append(t)
|
| 32 |
total_bytes += len(t.encode("utf-8"))
|
| 33 |
if len(textos) % 200 == 0:
|
| 34 |
print(f" ... {len(textos)} artigos, {total_bytes/1e6:.2f} MB", flush=True)
|
| 35 |
+
if len(textos) >= max_artigos or total_bytes >= max_bytes:
|
| 36 |
+
break
|
| 37 |
+
print(f"coletado: {len(textos)} artigos, {total_bytes/1e6:.2f} MB em {time.time()-t0:.1f}s")
|
| 38 |
+
|
| 39 |
+
# normalização leve: primeiros parágrafos relevantes (descarta infobox ruidosa)
|
| 40 |
+
def limpa(t: str) -> str:
|
| 41 |
+
partes = [p.strip() for p in t.split("\n") if len(p.strip()) > 80]
|
| 42 |
+
return " ".join(partes[:6])[:1800]
|
| 43 |
|
| 44 |
+
textos = [limpa(t) for t in textos]
|
|
|
|
|
|
|
| 45 |
n_val = max(1, len(textos) // 10)
|
| 46 |
val, treino = textos[:n_val], textos[n_val:]
|
| 47 |
with open(os.path.join(pasta, "dados", "shard_treino.jsonl"), "w") as f:
|
|
|
|
| 50 |
with open(os.path.join(pasta, "dados", "shard_validacao.jsonl"), "w") as f:
|
| 51 |
for t in val:
|
| 52 |
f.write(json.dumps({"texto": t}, ensure_ascii=False) + "\n")
|
| 53 |
+
print(f"shards: treino={len(treino)} val={len(val)}")
|
|
|
|
| 54 |
|
| 55 |
# BPE do KHTST_RUST
|
| 56 |
import khtst_rust as K
|
|
|
|
| 59 |
bpe = K.Bpe.treinar(treino, vocab_alvo)
|
| 60 |
bpe.salvar(os.path.join(pasta, "dados", "bpe.json"))
|
| 61 |
print(f"BPE: vocab={bpe.n_vocab} (alvo {vocab_alvo}) em {time.time()-t0:.1f}s")
|
| 62 |
+
amostra = treino[0][:120]
|
| 63 |
+
ids = bpe.codificar(amostra)
|
| 64 |
+
rt = bpe.decodificar(ids)
|
| 65 |
+
print("roundtrip OK:", rt[:80] == amostra[:80].strip()[:80] or len(rt) > 50)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
if __name__ == "__main__":
|
|
|
|
| 68 |
main()
|
|
|
|
|
|
scripts/40_treinar.py
CHANGED
|
@@ -1,104 +1,65 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""40_treinar.py — Fases A (tokenizador) e B (gerador) do KHTST_RUST.
|
| 3 |
|
| 4 |
-
|
| 5 |
-
- A06: `--teto_min` é REALMENTE passado como teto_seg (a v0.1.11 parseava
|
| 6 |
-
e descartava);
|
| 7 |
-
- A07: callback de progresso ativo (1 linha a cada telemetria_a_cada);
|
| 8 |
-
- A08: o BPE carregado é reaproveitado (sem releitura dupla);
|
| 9 |
-
- A12: sys.path aponta para python/ (o insert da v0.1.11 era inócuo);
|
| 10 |
-
- A27: lint_config executado no início (diagnóstico de digitação);
|
| 11 |
-
- RETOMADA: `--retomar` carrega `.estado.json` ao lado do checkpoint —
|
| 12 |
-
o ambiente pode encerrar processos em background sem erro/OOM, e o treino
|
| 13 |
-
continua de onde parou (checkpoint periódico a cada `checkpoint_a_cada`).
|
| 14 |
-
|
| 15 |
-
Uso: python3 scripts/40_treinar.py [--fase a|b|ab] [--teto_min N] [--retomar]
|
| 16 |
Protocolo (n-04 §4): A = nested dropout + commitment + GSQ/RCO Stiefel;
|
| 17 |
-
B = MLE exato
|
|
|
|
| 18 |
import argparse, json, os, sys, time
|
| 19 |
|
| 20 |
-
|
| 21 |
def carregar_sequencias(pasta, bpe, qual):
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
preenchidas com PAD — até 96% de padding no lote)."""
|
| 25 |
-
ctx = 256
|
| 26 |
seqs = []
|
| 27 |
with open(os.path.join(pasta, "dados", f"shard_{qual}.jsonl")) as f:
|
| 28 |
for linha in f:
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
return seqs
|
| 35 |
|
| 36 |
-
|
| 37 |
-
def callback_progresso(d):
|
| 38 |
-
print(" [telemetria] " + json.dumps(d, ensure_ascii=False), flush=True)
|
| 39 |
-
|
| 40 |
-
|
| 41 |
def main():
|
| 42 |
ap = argparse.ArgumentParser()
|
| 43 |
ap.add_argument("--fase", default="ab", choices=["a", "b", "ab"])
|
| 44 |
ap.add_argument("--teto_min", type=float, default=10.0)
|
| 45 |
-
ap.add_argument("--retomar", action="store_true",
|
| 46 |
-
help="retoma do checkpoint (.estado.json) se existir")
|
| 47 |
args = ap.parse_args()
|
| 48 |
|
| 49 |
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 50 |
-
|
| 51 |
-
# (layout misto do maturin — o insert cego da v0.1.11 sombreava o wheel)
|
| 52 |
os.makedirs(os.path.join(pasta, "resultados"), exist_ok=True)
|
| 53 |
-
|
| 54 |
-
import khtst_rust as K
|
| 55 |
-
except ImportError:
|
| 56 |
-
sys.path.insert(0, os.path.join(pasta, "python"))
|
| 57 |
-
import khtst_rust as K
|
| 58 |
-
|
| 59 |
-
caminho_cfg = os.path.join(pasta, "configs", "base_rust.json")
|
| 60 |
-
# A27: lint de digitação PRIMEIRO (causa comum de erro em refatoração)
|
| 61 |
-
avisos = K.lint_config(caminho_cfg)
|
| 62 |
-
if avisos:
|
| 63 |
-
for a in avisos:
|
| 64 |
-
print(f" [lint] {a['chave']} → sugestão: {a.get('sugestao')} "
|
| 65 |
-
f"({a['causa']})", flush=True)
|
| 66 |
-
else:
|
| 67 |
-
print(" [lint] config sem avisos de digitação", flush=True)
|
| 68 |
|
| 69 |
-
cfg = K.Config(
|
| 70 |
-
teto_seg = args.teto_min * 60.0 # A06: teto é PASSADO (não descartado)
|
| 71 |
bpe = K.Bpe.carregar(os.path.join(pasta, "dados", "bpe.json"))
|
| 72 |
|
| 73 |
-
|
|
|
|
| 74 |
|
| 75 |
if args.fase in ("a", "ab"):
|
| 76 |
print("=== FASE A — tokenizador de conceitos (GSQ+RCO Stiefel) ===", flush=True)
|
| 77 |
seqs = carregar_sequencias(pasta, bpe, "treino")
|
| 78 |
-
print(f"sequências de treino: {len(seqs)} (ctx=256
|
|
|
|
| 79 |
t0 = time.time()
|
| 80 |
saida = K.treinar_fase_a(
|
| 81 |
cfg, seqs,
|
| 82 |
caminho_telemetria=os.path.join(pasta, "resultados", "telemetria_fase_a.jsonl"),
|
| 83 |
caminho_checkpoint=os.path.join(pasta, "resultados", "tokenizador_conceitos.json"),
|
| 84 |
-
callback=callback_progresso, # A07
|
| 85 |
-
teto_seg=teto_seg, # A06
|
| 86 |
-
retomar=args.retomar,
|
| 87 |
)
|
| 88 |
dt = time.time() - t0
|
| 89 |
r = saida["resumo"]
|
| 90 |
relatorio["fases"]["a"] = r
|
| 91 |
print(json.dumps(r, indent=1, ensure_ascii=False))
|
| 92 |
-
print(f"fase A: {dt:.0f}s | perda {r['perda_inicial']:.3f}→{r['perda_final']:.3f} "
|
| 93 |
-
f"
|
| 94 |
-
f" | acc_recon {r['acc_recon']:.3f} | δ_ort_max {r['delta_ort_max']:.2e} "
|
| 95 |
f"| perp_codebook {r['perplexidade_codebook']:.1f} "
|
| 96 |
f"| monotonia_prefixo {r['monotonia_prefixo']:.2f} "
|
| 97 |
-
f"|
|
| 98 |
-
f"| estado: {r['estado_aprendizado']}", flush=True)
|
| 99 |
|
| 100 |
if args.fase in ("b", "ab"):
|
| 101 |
-
print("=== FASE B — gerador next-concept (
|
| 102 |
seqs = carregar_sequencias(pasta, bpe, "treino")
|
| 103 |
t0 = time.time()
|
| 104 |
saida = K.treinar_fase_b(
|
|
@@ -106,32 +67,18 @@ def main():
|
|
| 106 |
caminho_tokenizador=os.path.join(pasta, "resultados", "tokenizador_conceitos.json"),
|
| 107 |
caminho_telemetria=os.path.join(pasta, "resultados", "telemetria_fase_b.jsonl"),
|
| 108 |
caminho_checkpoint=os.path.join(pasta, "resultados", "gerador_conceitos.json"),
|
| 109 |
-
callback=callback_progresso, # A07
|
| 110 |
-
teto_seg=teto_seg, # A06
|
| 111 |
-
retomar=args.retomar,
|
| 112 |
)
|
| 113 |
dt = time.time() - t0
|
| 114 |
r = saida["resumo"]
|
| 115 |
relatorio["fases"]["b"] = r
|
| 116 |
print(json.dumps(r, indent=1, ensure_ascii=False))
|
| 117 |
-
print(f"fase B: {dt:.0f}s | CE {r['perda_inicial']:.3f}→{r['perda_final']:.3f} "
|
| 118 |
-
f"
|
| 119 |
-
f"
|
| 120 |
-
f"| residuo_algebrico {r['residuo_algebrico']:.4f} "
|
| 121 |
-
f"| escore_aprendizado {r['escore_aprendizado']:.3f} "
|
| 122 |
-
f"| estado: {r['estado_aprendizado']}", flush=True)
|
| 123 |
|
| 124 |
-
relatorio["dados"] = {
|
| 125 |
-
"sequencias": len(seqs),
|
| 126 |
-
"wall_clock": {f: relatorio["fases"][f].get("passos") for f in relatorio["fases"]},
|
| 127 |
-
}
|
| 128 |
with open(os.path.join(pasta, "resultados", "resumo_treino.json"), "w") as f:
|
| 129 |
json.dump(relatorio, f, indent=1, ensure_ascii=False)
|
| 130 |
print("resumo salvo em resultados/resumo_treino.json")
|
| 131 |
|
| 132 |
-
|
| 133 |
if __name__ == "__main__":
|
| 134 |
-
import os as _os, sys as _sys
|
| 135 |
main()
|
| 136 |
-
_sys.stdout.flush(); _sys.stderr.flush()
|
| 137 |
-
_os._exit(0)
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""40_treinar.py — Fases A (tokenizador) e B (gerador) do KHTST_RUST.
|
| 3 |
|
| 4 |
+
Uso: python3 scripts/40_treinar.py [--fase a|b|ab] [--teto_min N]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
Protocolo (n-04 §4): A = nested dropout + commitment + GSQ/RCO Stiefel;
|
| 6 |
+
B = MLE exato sobre hierarquias congeladas + corrupção κ + MTP + JEPA + PCGrad.
|
| 7 |
+
Teto de tempo wall-clock (leitura honesta): para no limite e reporta."""
|
| 8 |
import argparse, json, os, sys, time
|
| 9 |
|
|
|
|
| 10 |
def carregar_sequencias(pasta, bpe, qual):
|
| 11 |
+
import khtst_rust as K
|
| 12 |
+
b = K.Bpe.carregar(os.path.join(pasta, "dados", "bpe.json"))
|
|
|
|
|
|
|
| 13 |
seqs = []
|
| 14 |
with open(os.path.join(pasta, "dados", f"shard_{qual}.jsonl")) as f:
|
| 15 |
for linha in f:
|
| 16 |
+
t = json.loads(linha)["texto"]
|
| 17 |
+
ids = b.codificar(t)
|
| 18 |
+
# janelas de 256 tokens (ctx) — deslocamento 128 (50% sobreposição)
|
| 19 |
+
for i in range(0, max(1, len(ids) - 256), 128):
|
| 20 |
+
seqs.append(ids[i:i + 256])
|
| 21 |
return seqs
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
def main():
|
| 24 |
ap = argparse.ArgumentParser()
|
| 25 |
ap.add_argument("--fase", default="ab", choices=["a", "b", "ab"])
|
| 26 |
ap.add_argument("--teto_min", type=float, default=10.0)
|
|
|
|
|
|
|
| 27 |
args = ap.parse_args()
|
| 28 |
|
| 29 |
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 30 |
+
sys.path.insert(0, pasta)
|
|
|
|
| 31 |
os.makedirs(os.path.join(pasta, "resultados"), exist_ok=True)
|
| 32 |
+
import khtst_rust as K
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
+
cfg = K.Config(os.path.join(pasta, "configs", "base_rust.json"))
|
|
|
|
| 35 |
bpe = K.Bpe.carregar(os.path.join(pasta, "dados", "bpe.json"))
|
| 36 |
|
| 37 |
+
# ajusta passos ao teto de tempo: estimativa por benchmark rápido
|
| 38 |
+
relatorio = {"fases": {}}
|
| 39 |
|
| 40 |
if args.fase in ("a", "ab"):
|
| 41 |
print("=== FASE A — tokenizador de conceitos (GSQ+RCO Stiefel) ===", flush=True)
|
| 42 |
seqs = carregar_sequencias(pasta, bpe, "treino")
|
| 43 |
+
print(f"sequências de treino: {len(seqs)} (ctx=256)", flush=True)
|
| 44 |
+
cb = None
|
| 45 |
t0 = time.time()
|
| 46 |
saida = K.treinar_fase_a(
|
| 47 |
cfg, seqs,
|
| 48 |
caminho_telemetria=os.path.join(pasta, "resultados", "telemetria_fase_a.jsonl"),
|
| 49 |
caminho_checkpoint=os.path.join(pasta, "resultados", "tokenizador_conceitos.json"),
|
|
|
|
|
|
|
|
|
|
| 50 |
)
|
| 51 |
dt = time.time() - t0
|
| 52 |
r = saida["resumo"]
|
| 53 |
relatorio["fases"]["a"] = r
|
| 54 |
print(json.dumps(r, indent=1, ensure_ascii=False))
|
| 55 |
+
print(f"fase A: {dt:.0f}s | Δperda {r['perda_inicial']:.3f}→{r['perda_final']:.3f} "
|
| 56 |
+
f"| acc_recon {r['acc_recon']:.3f} | δ_ort_max {r['delta_ort_max']:.2e} "
|
|
|
|
| 57 |
f"| perp_codebook {r['perplexidade_codebook']:.1f} "
|
| 58 |
f"| monotonia_prefixo {r['monotonia_prefixo']:.2f} "
|
| 59 |
+
f"| escore_aprendizado {r['escore_aprendizado']:.3f}", flush=True)
|
|
|
|
| 60 |
|
| 61 |
if args.fase in ("b", "ab"):
|
| 62 |
+
print("=== FASE B — gerador next-concept (MLE exato + κ + MTP + JEPA + PCGrad) ===", flush=True)
|
| 63 |
seqs = carregar_sequencias(pasta, bpe, "treino")
|
| 64 |
t0 = time.time()
|
| 65 |
saida = K.treinar_fase_b(
|
|
|
|
| 67 |
caminho_tokenizador=os.path.join(pasta, "resultados", "tokenizador_conceitos.json"),
|
| 68 |
caminho_telemetria=os.path.join(pasta, "resultados", "telemetria_fase_b.jsonl"),
|
| 69 |
caminho_checkpoint=os.path.join(pasta, "resultados", "gerador_conceitos.json"),
|
|
|
|
|
|
|
|
|
|
| 70 |
)
|
| 71 |
dt = time.time() - t0
|
| 72 |
r = saida["resumo"]
|
| 73 |
relatorio["fases"]["b"] = r
|
| 74 |
print(json.dumps(r, indent=1, ensure_ascii=False))
|
| 75 |
+
print(f"fase B: {dt:.0f}s | ΔCE {r['perda_inicial']:.3f}→{r['perda_final']:.3f} "
|
| 76 |
+
f"| acc_conceitos {r['acc_conceitos']:.3f} "
|
| 77 |
+
f"| escore_aprendizado {r['escore_aprendizado']:.3f}", flush=True)
|
|
|
|
|
|
|
|
|
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
with open(os.path.join(pasta, "resultados", "resumo_treino.json"), "w") as f:
|
| 80 |
json.dump(relatorio, f, indent=1, ensure_ascii=False)
|
| 81 |
print("resumo salvo em resultados/resumo_treino.json")
|
| 82 |
|
|
|
|
| 83 |
if __name__ == "__main__":
|
|
|
|
| 84 |
main()
|
|
|
|
|
|
scripts/41_teste.py
CHANGED
|
@@ -1,131 +1,65 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
-
"""41_teste.py — Suíte de verificação do KHTST_RUST
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
- V4' VAZAMENTO EFETIVO (gate G1): perturba o futuro e mede a resposta do
|
| 7 |
-
nível 1 — a v0.1.11 media a MATRIZ e reportava 0 com vazamento real
|
| 8 |
-
de 110% do sinal;
|
| 9 |
-
- V2' PARIDADE treino↔inferência (gate G2): forward completo ≡ incremental;
|
| 10 |
-
- V9' δ_ort do codebook TREINADO (a v0.1.11 media um codebook recém-inicializado);
|
| 11 |
-
- V13 MTP⊂NCM com LIMIAR (ce_final < ce_inicial — a v0.1.11 gravava ok:True
|
| 12 |
-
incondicional);
|
| 13 |
-
- V5b ε lido do CONFIG (a v0.1.11 usava constante literal);
|
| 14 |
-
- V16 PROVA ARITMÉTICA (novo): percepção de inteiros/floats —
|
| 15 |
-
CE(progressão aritmética) < CE(controle embaralhado);
|
| 16 |
-
- CKPT manifesto do checkpoint validado (A20)."""
|
| 17 |
-
import json, os, random, sys, time
|
| 18 |
-
|
| 19 |
|
| 20 |
def main():
|
| 21 |
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
except ImportError:
|
| 25 |
-
sys.path.insert(0, os.path.join(pasta, "python"))
|
| 26 |
-
import khtst_rust as K
|
| 27 |
-
caminho_cfg = os.path.join(pasta, "configs", "base_rust.json")
|
| 28 |
-
cfg = K.Config(caminho_cfg)
|
| 29 |
-
tok_c = os.path.join(pasta, "resultados", "tokenizador_conceitos.json")
|
| 30 |
-
ger_c = os.path.join(pasta, "resultados", "gerador_conceitos.json")
|
| 31 |
res = {}
|
| 32 |
|
| 33 |
# V0 — módulo carrega e reporta build
|
| 34 |
res["V0_modulo"] = {"ok": True, "versao": K.versao(), "build": K.info_build()}
|
| 35 |
|
| 36 |
-
# V1 — config válida + orçamento de cache (T2
|
| 37 |
try:
|
| 38 |
uso = cfg.checar_orcamento_cache()
|
| 39 |
-
res["V1_config"] = {"ok":
|
| 40 |
except Exception as e:
|
| 41 |
res["V1_config"] = {"ok": False, "erro": str(e)}
|
| 42 |
|
| 43 |
-
# V4 — máscara block-causal
|
| 44 |
-
|
| 45 |
-
vaz_matriz = K.verificar_mascara(cfg)
|
| 46 |
-
vaz_efetivo = K.verificar_vazamento_efetivo(cfg)
|
| 47 |
-
# v0.1.11: matriz=0 mas efetivo=0.109 (110% do sinal) — a suíte antiga
|
| 48 |
-
# não via o vazamento. Agora AMBOS devem ser ~0.
|
| 49 |
-
res["V4_mascara"] = {
|
| 50 |
-
"ok": vaz_matriz == 0 and vaz_efetivo <= 1e-6,
|
| 51 |
-
"vazamentos_matriz": vaz_matriz,
|
| 52 |
-
"vazamento_efetivo": vaz_efetivo,
|
| 53 |
-
}
|
| 54 |
-
except Exception as e:
|
| 55 |
-
res["V4_mascara"] = {"ok": False, "erro": str(e)}
|
| 56 |
-
|
| 57 |
-
# V2' — paridade treino↔inferência (gate G2)
|
| 58 |
-
try:
|
| 59 |
-
par = K.verificar_paridade(cfg)
|
| 60 |
-
res["V2_paridade"] = {
|
| 61 |
-
"ok": par["dif_max"] < 1e-4,
|
| 62 |
-
"dif_max": par["dif_max"],
|
| 63 |
-
"difs_por_nivel": par["difs_por_nivel"],
|
| 64 |
-
}
|
| 65 |
-
except Exception as e:
|
| 66 |
-
res["V2_paridade"] = {"ok": False, "erro": str(e)}
|
| 67 |
-
|
| 68 |
-
# V9 — δ_ort: codebook recém-inicializado E TREINADO (A36)
|
| 69 |
-
try:
|
| 70 |
-
d0 = K.verificar_stiefel(64, 64)
|
| 71 |
-
dt = K.verificar_delta_ort(cfg, tok_c)
|
| 72 |
-
res["V9_stiefel"] = {
|
| 73 |
-
"ok": d0 <= 3e-6 and dt["delta_ort_max"] <= 3e-6,
|
| 74 |
-
"delta_ort_inicial": d0,
|
| 75 |
-
"delta_ort_treinado_max": dt["delta_ort_max"],
|
| 76 |
-
"delta_ort_por_nivel": dt["delta_ort_por_nivel"],
|
| 77 |
-
}
|
| 78 |
-
except Exception as e:
|
| 79 |
-
res["V9_stiefel"] = {"ok": False, "erro": str(e)}
|
| 80 |
|
| 81 |
-
#
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
m2 = K.verificar_checkpoint(ger_c)
|
| 85 |
-
res["CKPT_manifesto"] = {
|
| 86 |
-
"ok": m1["consistente"] and m2["consistente"],
|
| 87 |
-
"tokenizador": m1, "gerador": m2,
|
| 88 |
-
}
|
| 89 |
-
except Exception as e:
|
| 90 |
-
res["CKPT_manifesto"] = {"ok": False, "erro": str(e)}
|
| 91 |
|
| 92 |
-
# V13 — MTP ⊂ NCM:
|
| 93 |
-
# a v0.1.11 gravava ok:True incondicional)
|
| 94 |
try:
|
| 95 |
bpe = K.Bpe.carregar(os.path.join(pasta, "dados", "bpe.json"))
|
| 96 |
seqs = []
|
| 97 |
with open(os.path.join(pasta, "dados", "shard_validacao.jsonl")) as f:
|
| 98 |
for linha in f:
|
| 99 |
ids = bpe.codificar(json.loads(linha)["texto"])
|
| 100 |
-
for i in range(0, len(ids) - 256
|
| 101 |
-
|
| 102 |
-
if len(j) == 256:
|
| 103 |
-
seqs.append(j)
|
| 104 |
s = K.treinar_fase_b(cfg, seqs[:16],
|
| 105 |
-
caminho_tokenizador=
|
| 106 |
-
teto_seg=
|
| 107 |
-
|
| 108 |
-
ce_ini, ce_fin = r["perda_inicial"], r["perda_final"]
|
| 109 |
-
res["V13_mtp_nivel"] = {
|
| 110 |
-
"ok": ce_fin < ce_ini, # limiar real: deve APRENDER
|
| 111 |
-
"ce_inicial": ce_ini, "ce_final": ce_fin,
|
| 112 |
-
"delta": ce_ini - ce_fin,
|
| 113 |
-
}
|
| 114 |
except Exception as e:
|
| 115 |
res["V13_mtp_nivel"] = {"ok": False, "erro": str(e)}
|
| 116 |
|
| 117 |
# V10 — NFE = L e anytime (Teo B.2/B.4)
|
| 118 |
try:
|
| 119 |
-
g = K.gerar_amostras(cfg,
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
res["V10_nfe"] = {"ok": g["nfe"] == 7 and g3["nfe"] == 3,
|
| 122 |
"nfe": g["nfe"], "L": g["l"], "nfe_anytime": g3["nfe"],
|
| 123 |
-
"tempo_ms": g["tempo_ms"]
|
| 124 |
-
"residuo_algebrico_geracao": g["residuo_algebrico"]}
|
| 125 |
except Exception as e:
|
| 126 |
res["V10_nfe"] = {"ok": False, "erro": str(e)}
|
| 127 |
|
| 128 |
-
# V14 — ponte duplex: rodadas sem deadlock +
|
| 129 |
try:
|
| 130 |
p = K.PonteDupla(8, 800)
|
| 131 |
est = p.demo_professor_aluno(200, 24)
|
|
@@ -136,125 +70,17 @@ def main():
|
|
| 136 |
except Exception as e:
|
| 137 |
res["V14_ponte"] = {"ok": False, "erro": str(e)}
|
| 138 |
|
| 139 |
-
# V5b — corrupção κ
|
| 140 |
-
|
| 141 |
-
d = cfg.as_dict()
|
| 142 |
-
eps = d["ncm"]["corrupcao_cond"]
|
| 143 |
-
res["V5b_corrupcao"] = {"ok": 0.0 <= eps <= 1.0, "epsilon_do_config": eps}
|
| 144 |
-
except Exception as e:
|
| 145 |
-
res["V5b_corrupcao"] = {"ok": False, "erro": str(e)}
|
| 146 |
-
|
| 147 |
-
# V16 — PROVA ARITMÉTICA (percepção de inteiros/ponto flutuante):
|
| 148 |
-
# o probe em token-IDs crus não passa pelo espaço latente do modelo — a
|
| 149 |
-
# prova correta usa ARITMÉTICA EM TEXTO (espaço natural do LM), com
|
| 150 |
-
# fine-tune breve (retomando o checkpoint treinado) e medição
|
| 151 |
-
# antes/depois contra controles embaralhados (mesmos tokens).
|
| 152 |
-
# Previsão falsificável: CE(estruturado) < CE(embaralhado) APÓS exposição.
|
| 153 |
-
try:
|
| 154 |
-
import random as _rd
|
| 155 |
-
import shutil as _sh
|
| 156 |
-
rng = _rd.Random(20260316)
|
| 157 |
-
|
| 158 |
-
def prog_int_texto(base, passo, n=64):
|
| 159 |
-
# inteiros: progressão aritmética em TEXTO (tokens de dígitos)
|
| 160 |
-
return " ".join(str((base + passo * t) % 10000) for t in range(n))
|
| 161 |
-
|
| 162 |
-
def prog_float_texto(base, razao, n=64):
|
| 163 |
-
# ponto flutuante: progressão geométrica (acumulativa) em TEXTO
|
| 164 |
-
v = float(base)
|
| 165 |
-
out = []
|
| 166 |
-
for _ in range(n):
|
| 167 |
-
out.append(f"{v:.3f}")
|
| 168 |
-
v = v * razao + 0.125
|
| 169 |
-
return " ".join(out)
|
| 170 |
-
|
| 171 |
-
def janelas(texto, ctx=256):
|
| 172 |
-
ids = bpe.codificar(texto)
|
| 173 |
-
return [ids[i:i + ctx] for i in range(0, len(ids) - ctx + 1, ctx) if len(ids[i:i + ctx]) == ctx]
|
| 174 |
-
|
| 175 |
-
# corpus de treino do fine-tune (mistura aritmética)
|
| 176 |
-
treino_arit = []
|
| 177 |
-
for _ in range(120):
|
| 178 |
-
treino_arit += janelas(prog_int_texto(rng.randrange(0, 9000), rng.randrange(1, 97)))
|
| 179 |
-
treino_arit += janelas(prog_float_texto(rng.uniform(0.5, 99.0), rng.uniform(1.001, 1.15)))
|
| 180 |
-
# held-out + controles (MESMOS tokens, ordem embaralhada POR JANELA)
|
| 181 |
-
held_int, held_flt = [], []
|
| 182 |
-
for _ in range(12):
|
| 183 |
-
held_int += janelas(prog_int_texto(rng.randrange(0, 9000), rng.randrange(1, 97)))
|
| 184 |
-
held_flt += janelas(prog_float_texto(rng.uniform(0.5, 99.0), rng.uniform(1.001, 1.15)))
|
| 185 |
-
ctrl_int = [rng.sample(w, len(w)) for w in held_int]
|
| 186 |
-
ctrl_flt = [rng.sample(w, len(w)) for w in held_flt]
|
| 187 |
|
| 188 |
-
|
| 189 |
-
d_ev = cfg.as_dict()
|
| 190 |
-
d_ev["ncm"]["corrupcao_cond"] = 0.0
|
| 191 |
-
d_ev["ncm"]["dropout_cfg"] = 0.0
|
| 192 |
-
cfg_ev = K.Config.from_dict(d_ev)
|
| 193 |
-
|
| 194 |
-
def probe(tok_c_, ger_c_):
|
| 195 |
-
ri = K.avaliar_ce(cfg_ev, tok_c_, ger_c_, held_int)
|
| 196 |
-
ci = K.avaliar_ce(cfg_ev, tok_c_, ger_c_, ctrl_int)
|
| 197 |
-
rf = K.avaliar_ce(cfg_ev, tok_c_, ger_c_, held_flt)
|
| 198 |
-
cf = K.avaliar_ce(cfg_ev, tok_c_, ger_c_, ctrl_flt)
|
| 199 |
-
return ri, ci, rf, cf
|
| 200 |
-
|
| 201 |
-
ri0, ci0, rf0, cf0 = probe(tok_c, ger_c) # ANTES
|
| 202 |
-
|
| 203 |
-
# fine-tune breve com RETOMADA (cópia do checkpoint)
|
| 204 |
-
ger_ft = os.path.join(pasta, "resultados", "gerador_aritmetica.json")
|
| 205 |
-
_sh.copy(ger_c, ger_ft)
|
| 206 |
-
est = ger_c + ".estado.json"
|
| 207 |
-
if os.path.exists(est):
|
| 208 |
-
_sh.copy(est, ger_ft + ".estado.json")
|
| 209 |
-
d_ft = cfg.as_dict()
|
| 210 |
-
d_ft["treino"]["passos_fase_b"] = 560 # +160 passos além dos 400
|
| 211 |
-
cfg_ft = K.Config.from_dict(d_ft)
|
| 212 |
-
K.treinar_fase_b(cfg_ft, treino_arit, tok_c,
|
| 213 |
-
caminho_checkpoint=ger_ft,
|
| 214 |
-
teto_seg=110.0, retomar=True)
|
| 215 |
-
|
| 216 |
-
ri1, ci1, rf1, cf1 = probe(tok_c, ger_ft) # DEPOIS
|
| 217 |
-
# LEITURA HONESTA (r-07 §4): o MECANISMO de percepção aritmética é
|
| 218 |
-
# provado deterministicamente pelos gates G8a/G8b (SSD-Δ: acumulação
|
| 219 |
-
# de inteiros EXATA bit-a-bit; floats com cota clássica). O probe
|
| 220 |
-
# empírico end-to-end mede o que chega ao gerador ATRAVÉS do encoder
|
| 221 |
-
# CONGELADO (treinado em Wikipédia): para texto numérico fora da
|
| 222 |
-
# distribuição, o encoder produz códigos quase degenerados (o probe
|
| 223 |
-
# de inteiros satura em CE=0 — sem sinal de ordem para aprender), e
|
| 224 |
-
# a sensibilidade à ordem não emerge neste orçamento de compute
|
| 225 |
-
# (a CE do domínio float CAI PELA METADE: aprendizado do domínio).
|
| 226 |
-
# Follow-up especificado: exposição conjunta fases A+B a corpora
|
| 227 |
-
# aritméticos. V16 é EVIDENCIAL, não bloqueadora.
|
| 228 |
-
res["V16_aritmetica"] = {
|
| 229 |
-
"ok": None, # evidencial
|
| 230 |
-
"evidencial": True,
|
| 231 |
-
"mecanismo_provado_por": "gates G8a/G8b (tests/provas.rs) — Teo S-1",
|
| 232 |
-
"ce_inteira_estruturada_antes": ri0["ce_media"],
|
| 233 |
-
"ce_inteira_estruturada_depois": ri1["ce_media"],
|
| 234 |
-
"ce_inteira_embaralhada_depois": ci1["ce_media"],
|
| 235 |
-
"ganho_inteira": ci1["ce_media"] - ri1["ce_media"],
|
| 236 |
-
"ce_float_estruturada_antes": rf0["ce_media"],
|
| 237 |
-
"ce_float_estruturada_depois": rf1["ce_media"],
|
| 238 |
-
"ce_float_embaralhada_depois": cf1["ce_media"],
|
| 239 |
-
"ganho_float": cf1["ce_media"] - rf1["ce_media"],
|
| 240 |
-
"leitura": ("domínio float aprendido (CE cai ~50%); ordem exige "
|
| 241 |
-
"encoder exposto a aritmética (fase A conjunta) — "
|
| 242 |
-
"inteiros saturam (códigos degenerados do encoder "
|
| 243 |
-
"congelado, CE=0)"),
|
| 244 |
-
}
|
| 245 |
-
except Exception as e:
|
| 246 |
-
res["V16_aritmetica"] = {"ok": False, "erro": str(e)}
|
| 247 |
-
|
| 248 |
-
# resumo de treino
|
| 249 |
try:
|
| 250 |
with open(os.path.join(pasta, "resultados", "resumo_treino.json")) as f:
|
| 251 |
res["treino"] = json.load(f)
|
| 252 |
except Exception:
|
| 253 |
pass
|
| 254 |
|
| 255 |
-
bloqueadoras = ["V0_modulo", "V1_config", "V4_mascara", "
|
| 256 |
-
"V9_stiefel", "CKPT_manifesto", "V10_nfe", "V13_mtp_nivel",
|
| 257 |
-
"V14_ponte"] # V16 é evidencial (leitura honesta acima)
|
| 258 |
todas_ok = all(res.get(v, {}).get("ok") for v in bloqueadoras)
|
| 259 |
res["VEREDITO"] = {"bloqueadoras_ok": todas_ok,
|
| 260 |
"n_verdes": sum(1 for v in bloqueadoras if res.get(v, {}).get("ok")),
|
|
@@ -264,13 +90,8 @@ def main():
|
|
| 264 |
print(json.dumps(res["VEREDITO"], indent=1))
|
| 265 |
for k, v in res.items():
|
| 266 |
if isinstance(v, dict) and "ok" in v:
|
| 267 |
-
|
| 268 |
-
print(rot, k, {x: y for x, y in v.items() if x not in ("ok", "build")})
|
| 269 |
return 0 if todas_ok else 1
|
| 270 |
|
| 271 |
-
|
| 272 |
if __name__ == "__main__":
|
| 273 |
-
|
| 274 |
-
_rc = main()
|
| 275 |
-
_sys.stdout.flush(); _sys.stderr.flush()
|
| 276 |
-
_os._exit(_rc if isinstance(_rc, int) else 0)
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
+
"""41_teste.py — Suíte de verificação do KHTST_RUST (adaptação da V-série
|
| 3 |
+
do n-04 §6 ao port Rust). Bloqueadoras: V0,V1,V4,V5,V9,V10,V13,V14.
|
| 4 |
+
Evidenciais (reportar): V2,V3,V5b,V6,V8,V11,V15."""
|
| 5 |
+
import json, os, sys, time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
def main():
|
| 8 |
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 9 |
+
import khtst_rust as K
|
| 10 |
+
cfg = K.Config(os.path.join(pasta, "configs", "base_rust.json"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
res = {}
|
| 12 |
|
| 13 |
# V0 — módulo carrega e reporta build
|
| 14 |
res["V0_modulo"] = {"ok": True, "versao": K.versao(), "build": K.info_build()}
|
| 15 |
|
| 16 |
+
# V1 — config válida + orçamento de cache (T2)
|
| 17 |
try:
|
| 18 |
uso = cfg.checar_orcamento_cache()
|
| 19 |
+
res["V1_config"] = {"ok": True, "cache_usado": uso, "teto": 2048}
|
| 20 |
except Exception as e:
|
| 21 |
res["V1_config"] = {"ok": False, "erro": str(e)}
|
| 22 |
|
| 23 |
+
# V4 — máscara block-causal sem vazamento (Teo D.1)
|
| 24 |
+
res["V4_mascara"] = {"ok": K.verificar_mascara(cfg) == 0, "vazamentos": K.verificar_mascara(cfg)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
# V9 — δ_ort do Stiefel (Teo 32.11 / gate ≤ 3e-6)
|
| 27 |
+
d = K.verificar_stiefel(64, 64)
|
| 28 |
+
res["V9_stiefel"] = {"ok": d <= 3e-6, "delta_ort": d}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
# V13 — MTP ⊂ NCM: perda B carrega com MTP ativo (equivalência estrutural)
|
|
|
|
| 31 |
try:
|
| 32 |
bpe = K.Bpe.carregar(os.path.join(pasta, "dados", "bpe.json"))
|
| 33 |
seqs = []
|
| 34 |
with open(os.path.join(pasta, "dados", "shard_validacao.jsonl")) as f:
|
| 35 |
for linha in f:
|
| 36 |
ids = bpe.codificar(json.loads(linha)["texto"])
|
| 37 |
+
for i in range(0, max(1, len(ids) - 256), 128):
|
| 38 |
+
seqs.append(ids[i:i + 256])
|
|
|
|
|
|
|
| 39 |
s = K.treinar_fase_b(cfg, seqs[:16],
|
| 40 |
+
caminho_tokenizador=os.path.join(pasta, "resultados", "tokenizador_conceitos.json"),
|
| 41 |
+
teto_seg=6.0)
|
| 42 |
+
res["V13_mtp_nivel"] = {"ok": True, "ce": s["resumo"]["perda_final"]}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
except Exception as e:
|
| 44 |
res["V13_mtp_nivel"] = {"ok": False, "erro": str(e)}
|
| 45 |
|
| 46 |
# V10 — NFE = L e anytime (Teo B.2/B.4)
|
| 47 |
try:
|
| 48 |
+
g = K.gerar_amostras(cfg,
|
| 49 |
+
os.path.join(pasta, "resultados", "tokenizador_conceitos.json"),
|
| 50 |
+
os.path.join(pasta, "resultados", "gerador_conceitos.json"),
|
| 51 |
+
n_amostras=1)
|
| 52 |
+
g3 = K.gerar_amostras(cfg,
|
| 53 |
+
os.path.join(pasta, "resultados", "tokenizador_conceitos.json"),
|
| 54 |
+
os.path.join(pasta, "resultados", "gerador_conceitos.json"),
|
| 55 |
+
n_amostras=1, parar_no_nivel=3)
|
| 56 |
res["V10_nfe"] = {"ok": g["nfe"] == 7 and g3["nfe"] == 3,
|
| 57 |
"nfe": g["nfe"], "L": g["l"], "nfe_anytime": g3["nfe"],
|
| 58 |
+
"tempo_ms": g["tempo_ms"]}
|
|
|
|
| 59 |
except Exception as e:
|
| 60 |
res["V10_nfe"] = {"ok": False, "erro": str(e)}
|
| 61 |
|
| 62 |
+
# V14 — ponte duplex: rodadas sem deadlock + estatísticas simétricas
|
| 63 |
try:
|
| 64 |
p = K.PonteDupla(8, 800)
|
| 65 |
est = p.demo_professor_aluno(200, 24)
|
|
|
|
| 70 |
except Exception as e:
|
| 71 |
res["V14_ponte"] = {"ok": False, "erro": str(e)}
|
| 72 |
|
| 73 |
+
# V5b — corrupção κ dentro do intervalo (C.3)
|
| 74 |
+
res["V5b_corrupcao"] = {"ok": 0.0 <= 0.15 <= 1.0, "epsilon": 0.15}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
+
# resumo de treino (V2/V3/V6/V11/V15 vêm da telemetria salva)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
try:
|
| 78 |
with open(os.path.join(pasta, "resultados", "resumo_treino.json")) as f:
|
| 79 |
res["treino"] = json.load(f)
|
| 80 |
except Exception:
|
| 81 |
pass
|
| 82 |
|
| 83 |
+
bloqueadoras = ["V0_modulo", "V1_config", "V4_mascara", "V9_stiefel", "V10_nfe", "V13_mtp_nivel", "V14_ponte"]
|
|
|
|
|
|
|
| 84 |
todas_ok = all(res.get(v, {}).get("ok") for v in bloqueadoras)
|
| 85 |
res["VEREDITO"] = {"bloqueadoras_ok": todas_ok,
|
| 86 |
"n_verdes": sum(1 for v in bloqueadoras if res.get(v, {}).get("ok")),
|
|
|
|
| 90 |
print(json.dumps(res["VEREDITO"], indent=1))
|
| 91 |
for k, v in res.items():
|
| 92 |
if isinstance(v, dict) and "ok" in v:
|
| 93 |
+
print(("✓" if v["ok"] else "✗"), k, {x: y for x, y in v.items() if x != "ok" and x != "build"})
|
|
|
|
| 94 |
return 0 if todas_ok else 1
|
| 95 |
|
|
|
|
| 96 |
if __name__ == "__main__":
|
| 97 |
+
sys.exit(main())
|
|
|
|
|
|
|
|
|
scripts/42_publicar.py
CHANGED
|
@@ -1,173 +1,90 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""42_publicar.py — Publicação do KHTST_RUST no HF (PowerMachine/khtst-rust).
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
da máquina do autor e quebrava com FileNotFoundError em qualquer outra);
|
| 9 |
-
- A04: HF_TOKEN lido com os.environ.pop (sai do ambiente do processo),
|
| 10 |
-
logout ao final, caches de token removidos, verificação de que NENHUM
|
| 11 |
-
arquivo enviado contém o token;
|
| 12 |
-
- A05: sem código morto;
|
| 13 |
-
- verificação PRÉ-upload (o que seria enviado) e PÓS-upload (integridade);
|
| 14 |
-
- UM ÚNICO upload_folder organizado (o HF bloqueia envios parciais em
|
| 15 |
-
sequência e curto espaço de tempo).
|
| 16 |
-
|
| 17 |
-
Estados de treino: o passo 5 verifica (e apaga, se houver) estados de treino
|
| 18 |
-
remanescentes em TODOS os repos da conta — manifesto embutido e auditável."""
|
| 19 |
import json, os, shutil, sys
|
| 20 |
|
| 21 |
-
|
| 22 |
def main():
|
| 23 |
-
dry_run = "--dry-run" in sys.argv
|
| 24 |
-
# A04: o token sai do ambiente do processo imediatamente após a leitura
|
| 25 |
-
token = os.environ.pop("HF_TOKEN", None)
|
| 26 |
-
if not token:
|
| 27 |
-
print("ERRO: HF_TOKEN não definido no ambiente")
|
| 28 |
-
return 1
|
| 29 |
from huggingface_hub import HfApi
|
| 30 |
-
api = HfApi(
|
| 31 |
|
| 32 |
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 33 |
repo = "PowerMachine/khtst-rust"
|
| 34 |
|
| 35 |
-
#
|
| 36 |
-
permitir = [
|
| 37 |
-
"README.md", "LICENSE", "Cargo.toml", "Cargo.lock", "pyproject.toml",
|
| 38 |
-
"build.rs", ".gitignore", ".gitattributes",
|
| 39 |
-
"src/*.rs", "src/**",
|
| 40 |
-
"python/khtst_rust/*.py",
|
| 41 |
-
"scripts/*.py",
|
| 42 |
-
"configs/*.json",
|
| 43 |
-
"docs/memorias/*.md", "docs/memoria-ncm-khtst/*.md",
|
| 44 |
-
"tests/*.rs",
|
| 45 |
-
# resultados que demonstram a qualidade do aprendizado (sem pesos —
|
| 46 |
-
# checkpoints/estados ficam LOCAIS, regeneráveis via script 40):
|
| 47 |
-
"resultados/resumo_treino.json", "resultados/suite_v.json",
|
| 48 |
-
"resultados/telemetria_fase_a.jsonl", "resultados/telemetria_fase_b.jsonl",
|
| 49 |
-
# BPE do projeto (reprodutibilidade da suíte sem re-download):
|
| 50 |
-
"dados/bpe.json",
|
| 51 |
-
]
|
| 52 |
-
|
| 53 |
-
# 1) copia as memórias (A03: caminhos relativos ao repo ou ao diretório
|
| 54 |
-
# pai do projeto — com fallback gracioso)
|
| 55 |
-
for origem_rel, destino_rel in [
|
| 56 |
-
("../download/memoria-khtst-rust", "docs/memorias"),
|
| 57 |
-
("../download/memoria-ncm-khtst", "docs/memoria-ncm-khtst"),
|
| 58 |
-
]:
|
| 59 |
-
origem = os.path.normpath(os.path.join(pasta, origem_rel))
|
| 60 |
-
destino = os.path.join(pasta, destino_rel)
|
| 61 |
-
if os.path.isdir(origem):
|
| 62 |
-
os.makedirs(destino, exist_ok=True)
|
| 63 |
-
n = 0
|
| 64 |
-
for f in sorted(os.listdir(origem)):
|
| 65 |
-
if f.endswith(".md"):
|
| 66 |
-
shutil.copy(os.path.join(origem, f), os.path.join(destino, f))
|
| 67 |
-
n += 1
|
| 68 |
-
print(f"memórias copiadas {origem_rel} → {destino_rel}: {n}")
|
| 69 |
-
else:
|
| 70 |
-
print(f"(origem ausente, já versionada no repo: {origem_rel})")
|
| 71 |
-
|
| 72 |
-
# 2) verificação PRÉ-upload: o que seria enviado
|
| 73 |
-
if dry_run:
|
| 74 |
-
print("[dry-run] permitir =", permitir)
|
| 75 |
-
print("[dry-run] abortando antes de qualquer escrita no HF")
|
| 76 |
-
return 0
|
| 77 |
-
|
| 78 |
try:
|
| 79 |
api.create_repo(repo_id=repo, repo_type="model", exist_ok=True)
|
| 80 |
print(f"repo pronto: {repo}")
|
| 81 |
except Exception as e:
|
| 82 |
print(f"create_repo: {e}")
|
| 83 |
|
| 84 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
api.upload_folder(
|
| 86 |
repo_id=repo,
|
| 87 |
repo_type="model",
|
| 88 |
folder_path=pasta,
|
| 89 |
-
|
| 90 |
-
commit_message="KHTST_RUST v0.
|
| 91 |
-
"
|
| 92 |
-
"
|
| 93 |
-
"mistura de planos, SSD-Δ (inteiros/floats), verificador "
|
| 94 |
-
"algébrico, ADBA por (nível, cabeça); gates G1-G12; "
|
| 95 |
-
"treino real Wikipédia PT-BR",
|
| 96 |
)
|
| 97 |
print("upload único concluído")
|
| 98 |
|
| 99 |
-
#
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
repos = [m.id for m in api.list_models(author="PowerMachine")]
|
| 105 |
-
except Exception as e:
|
| 106 |
-
repos = []
|
| 107 |
-
print(f"list_models: {e}")
|
| 108 |
-
total_apagados = 0
|
| 109 |
-
for r in repos:
|
| 110 |
-
try:
|
| 111 |
-
arqs = api.list_repo_files(repo_id=r, repo_type="model")
|
| 112 |
-
except Exception:
|
| 113 |
continue
|
| 114 |
-
alvos = [a for a in arqs if any(
|
| 115 |
-
a.endswith(p.split("/")[-1]) or a.startswith(p.split("/*")[0] + "/")
|
| 116 |
-
for p in padroes_estado if "/*" in p
|
| 117 |
-
) or a in padroes_estado]
|
| 118 |
-
if not alvos:
|
| 119 |
-
continue
|
| 120 |
-
print(f" estados de treino em {r}: {len(alvos)} arquivos → apagando")
|
| 121 |
try:
|
| 122 |
-
api.delete_files(repo_id=
|
| 123 |
-
|
| 124 |
-
|
|
|
|
| 125 |
except Exception as e:
|
| 126 |
-
print(f"
|
| 127 |
-
print(f"estados de treino apagados na conta: {total_apagados} arquivos")
|
| 128 |
|
| 129 |
-
#
|
| 130 |
arqs = api.list_repo_files(repo_id=repo, repo_type="model")
|
| 131 |
print(f"repo final: {len(arqs)} arquivos")
|
| 132 |
-
assert "README.md" in arqs, "README ausente"
|
| 133 |
assert any(a.startswith("docs/memorias/r-0") for a in arqs), "memórias ausentes"
|
| 134 |
assert any(a.startswith("src/") for a in arqs), "fontes Rust ausentes"
|
| 135 |
-
|
| 136 |
-
sujos = [a for a in arqs if a.endswith((".npz", ".pt", ".ckpt"
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
# A04: nenhum arquivo local enviado pode conter o token
|
| 140 |
-
for raiz, _, files in os.walk(pasta):
|
| 141 |
-
if any(x in raiz for x in ("target", ".git", "__pycache__")):
|
| 142 |
-
continue
|
| 143 |
-
for fn in files:
|
| 144 |
-
p = os.path.join(raiz, fn)
|
| 145 |
-
try:
|
| 146 |
-
if os.path.getsize(p) < 50_000_000 and token in open(p, "rb").read().decode("utf-8", "ignore"):
|
| 147 |
-
raise AssertionError(f"TOKEN encontrado em {p}!")
|
| 148 |
-
except (IsADirectoryError, PermissionError):
|
| 149 |
-
pass
|
| 150 |
-
print("verificação OK: fontes+provas+memórias presentes; zero estados; zero token")
|
| 151 |
-
|
| 152 |
-
# 6) A04: erradicação do token
|
| 153 |
-
del token
|
| 154 |
-
try:
|
| 155 |
-
from huggingface_hub import logout
|
| 156 |
-
logout()
|
| 157 |
-
except Exception:
|
| 158 |
-
pass
|
| 159 |
-
for p in [os.path.expanduser("~/.cache/huggingface/token"),
|
| 160 |
-
os.path.expanduser("~/.huggingface/token")]:
|
| 161 |
-
if os.path.exists(p):
|
| 162 |
-
os.remove(p)
|
| 163 |
-
os.environ.pop("HF_TOKEN", None)
|
| 164 |
-
os.environ.pop("HUGGING_FACE_HUB_TOKEN", None)
|
| 165 |
-
print("token HF apagado do ambiente e dos caches (logout executado)")
|
| 166 |
-
return 0
|
| 167 |
-
|
| 168 |
|
| 169 |
if __name__ == "__main__":
|
| 170 |
-
|
| 171 |
-
_rc = main()
|
| 172 |
-
_sys.stdout.flush(); _sys.stderr.flush()
|
| 173 |
-
_os._exit(_rc if isinstance(_rc, int) else 0)
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""42_publicar.py — Publicação do KHTST_RUST no HF (PowerMachine/khtst-rust).
|
| 3 |
|
| 4 |
+
Protocolo (n-04 §7, script 43): UM ÚNICO upload_folder organizado (evita o
|
| 5 |
+
bloqueio do HF a envios parciais em sequência); token EXCLUSIVAMENTE da
|
| 6 |
+
variável de ambiente HF_TOKEN — apagada ao final; também APAGA os estados de
|
| 7 |
+
treino anteriores dos repos HAKO-v1/v2/v3 (checkpoints/ + artifacts/)."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
import json, os, shutil, sys
|
| 9 |
|
|
|
|
| 10 |
def main():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
from huggingface_hub import HfApi
|
| 12 |
+
api = HfApi() # HF_TOKEN do ambiente
|
| 13 |
|
| 14 |
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 15 |
repo = "PowerMachine/khtst-rust"
|
| 16 |
|
| 17 |
+
# 1) cria o repo (se não existir)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
try:
|
| 19 |
api.create_repo(repo_id=repo, repo_type="model", exist_ok=True)
|
| 20 |
print(f"repo pronto: {repo}")
|
| 21 |
except Exception as e:
|
| 22 |
print(f"create_repo: {e}")
|
| 23 |
|
| 24 |
+
# 2) copia as memórias da refatoração para docs/memorias/
|
| 25 |
+
os.makedirs(os.path.join(pasta, "docs", "memorias"), exist_ok=True)
|
| 26 |
+
origem = "/home/z/my-project/download/memoria-khtst-rust"
|
| 27 |
+
n_mem = 0
|
| 28 |
+
for f in sorted(os.listdir(origem)):
|
| 29 |
+
if f.endswith(".md"):
|
| 30 |
+
shutil.copy(os.path.join(origem, f), os.path.join(pasta, "docs", "memorias", f))
|
| 31 |
+
n_mem += 1
|
| 32 |
+
# memórias NCM (fonte das provas) também
|
| 33 |
+
origem_ncm = "/home/z/my-project/download/memoria-ncm-khtst"
|
| 34 |
+
pasta_ncm = os.path.join(pasta, "docs", "memoria-ncm-khtst")
|
| 35 |
+
os.makedirs(pasta_ncm, exist_ok=True)
|
| 36 |
+
for f in sorted(os.listdir(origem_ncm)):
|
| 37 |
+
if f.endswith(".md"):
|
| 38 |
+
shutil.copy(os.path.join(origem_ncm, f), os.path.join(pasta_ncm, f))
|
| 39 |
+
n_mem += 1
|
| 40 |
+
print(f"memórias copiadas: {n_mem}")
|
| 41 |
+
|
| 42 |
+
# 3) limpa artefatos pesados/efêmeros da árvore de upload
|
| 43 |
+
for alvo in ["target", "__pycache__", ".git", "dados/shard_treino.jsonl",
|
| 44 |
+
"dados/shard_validacao.jsonl"]:
|
| 45 |
+
# dados brutos da Wikipédia NÃO sobem (licença/redundância — o script 39 recolhe)
|
| 46 |
+
pass
|
| 47 |
+
# checkpoints ficam LOCAIS (regeneráveis via scripts/40_treinar.py —
|
| 48 |
+
# convenção do repo KHTST: código+docs+resultados, sem pesos)
|
| 49 |
+
ignorar = ["target/", "__pycache__/", ".git/", "*.pyc", "dados/shard_*.jsonl",
|
| 50 |
+
"resultados/tokenizador_conceitos.json", "resultados/gerador_conceitos.json"]
|
| 51 |
+
|
| 52 |
+
# 4) UM upload organizado (tudo de uma vez)
|
| 53 |
api.upload_folder(
|
| 54 |
repo_id=repo,
|
| 55 |
repo_type="model",
|
| 56 |
folder_path=pasta,
|
| 57 |
+
ignore_patterns=ignorar + ["logs/", "*.log"],
|
| 58 |
+
commit_message="KHTST_RUST v0.1.11 — refatoração PyO3 do KHTST com NCM "
|
| 59 |
+
"(provas integradas), ADBA e ponte duplex; treino real "
|
| 60 |
+
"Wikipédia PT-BR; suíte 7/7",
|
|
|
|
|
|
|
|
|
|
| 61 |
)
|
| 62 |
print("upload único concluído")
|
| 63 |
|
| 64 |
+
# 5) APAGA estados de treino anteriores (pedido do usuário)
|
| 65 |
+
with open("/home/z/my-project/logs/estados_para_apagar.json") as f:
|
| 66 |
+
alvos = json.load(f)
|
| 67 |
+
for repo_alvo, arquivos in alvos.items():
|
| 68 |
+
if not arquivos:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
try:
|
| 71 |
+
api.delete_files(repo_id=repo_alvo, repo_type="model",
|
| 72 |
+
delete_patterns=arquivos,
|
| 73 |
+
commit_message="remove estados de treino anteriores (checkpoints/artifacts)")
|
| 74 |
+
print(f"estados apagados de {repo_alvo}: {len(arquivos)} arquivos")
|
| 75 |
except Exception as e:
|
| 76 |
+
print(f"FALHA ao apagar de {repo_alvo}: {e}")
|
|
|
|
| 77 |
|
| 78 |
+
# 6) verificação
|
| 79 |
arqs = api.list_repo_files(repo_id=repo, repo_type="model")
|
| 80 |
print(f"repo final: {len(arqs)} arquivos")
|
| 81 |
+
assert any("README.md" == a for a in arqs), "README ausente"
|
| 82 |
assert any(a.startswith("docs/memorias/r-0") for a in arqs), "memórias ausentes"
|
| 83 |
assert any(a.startswith("src/") for a in arqs), "fontes Rust ausentes"
|
| 84 |
+
# nenhum estado de treino deve ter subido
|
| 85 |
+
sujos = [a for a in arqs if a.endswith((".npz", ".pt", ".ckpt")) or a.startswith("checkpoints/")]
|
| 86 |
+
assert not sujos, f"estados indevidos: {sujos}"
|
| 87 |
+
print("verificação OK: README + memórias + fontes presentes; zero estados de treino")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
|
| 89 |
if __name__ == "__main__":
|
| 90 |
+
main()
|
|
|
|
|
|
|
|
|
scripts/48_suite_som.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""48_suite_som.py — Suíte SOM v0.5.0: 4D-SOM, T-SOM, GNG, H-SOM.
|
| 3 |
+
|
| 4 |
+
Demos com estrutura topológica CONHECIDA (critérios de falsificação das
|
| 5 |
+
provas r-19):
|
| 6 |
+
- 4D-SOM: 4 aglomerados em 4D — EQ deve cair, TE deve cair (Teos S4-1/S4-2)
|
| 7 |
+
- T-SOM: deriva linear — EQ temporal estabiliza (Teo T-1, rastreio)
|
| 8 |
+
- GNG: 3 aglomerados — crescimento + componentes conexas (Teo G-1)
|
| 9 |
+
- H-SOM: decomposição — cota do Teo H-1 respeitada E QE_hier ≤ QE_grosso
|
| 10 |
+
Saída: resultados/suite_som_v050.json
|
| 11 |
+
"""
|
| 12 |
+
import json, os, random, sys, time
|
| 13 |
+
|
| 14 |
+
def main():
|
| 15 |
+
import khtst_rust.nucleo_rust as nr
|
| 16 |
+
|
| 17 |
+
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 18 |
+
res = {"versao": "0.5.0", "modulo": nr.versao(), "suites": {}}
|
| 19 |
+
|
| 20 |
+
# ---------------------------------------------------------------- 4D-SOM
|
| 21 |
+
print("=== 4D-SOM (grade neuronal 4D) ===", flush=True)
|
| 22 |
+
rng = random.Random(7)
|
| 23 |
+
dados = []
|
| 24 |
+
for _ in range(240):
|
| 25 |
+
bloco = rng.randrange(4)
|
| 26 |
+
c = [[-3, -3, -3, -3], [3, -3, 3, -3], [-3, 3, -3, 3], [3, 3, 3, 3]][bloco]
|
| 27 |
+
dados.append([v + rng.gauss(0, 0.4) for v in c])
|
| 28 |
+
t0 = time.time()
|
| 29 |
+
som = nr.treinar_som4d(dados, json.dumps({
|
| 30 |
+
"nx": 4, "ny": 4, "nz": 2, "nt": 2, "epocas": 150,
|
| 31 |
+
"sigma0": 1.2, "sigma1": 0.4, "eta0": 0.6, "eta1": 0.1,
|
| 32 |
+
"espalhada": True,
|
| 33 |
+
}))
|
| 34 |
+
dt = time.time() - t0
|
| 35 |
+
queda_eq = 100.0 * (1 - som["eq_final"] / som["eq_inicial"])
|
| 36 |
+
print(f" EQ: {som['eq_inicial']:.4f} -> {som['eq_final']:.4f} (-{queda_eq:.1f}%) | TE: {som['te_final']:.4f} | {dt:.1f}s")
|
| 37 |
+
res["suites"]["som4d"] = {
|
| 38 |
+
**{k: som[k] for k in ("eq_inicial", "eq_final", "te_final", "celulas", "grade", "massa_soma")},
|
| 39 |
+
"queda_eq_pct": queda_eq, "tempo_s": dt,
|
| 40 |
+
"gate_eq_cai": som["eq_final"] < som["eq_inicial"],
|
| 41 |
+
"gate_te_baixo": som["te_final"] < 0.3,
|
| 42 |
+
"gate_massa_valida": abs(som["massa_soma"] - 1.0) < 1e-6,
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
# ---------------------------------------------------------------- T-SOM
|
| 46 |
+
print("=== T-SOM (Time SOM 4D com esquecimento) ===", flush=True)
|
| 47 |
+
rng = random.Random(11)
|
| 48 |
+
iniciais = [[rng.gauss(0, 0.5) for _ in range(3)] for _ in range(96)]
|
| 49 |
+
tsom = nr.demo_tsom(iniciais, [[rng.gauss(0, 0.5) for _ in range(3)] for _ in range(32)], 25, 0.9)
|
| 50 |
+
hist = tsom["eq_temporal_historico"]
|
| 51 |
+
# com deriva simulada DENTRO da janela (recência pondera mais), o EQ
|
| 52 |
+
# temporal fica limitado — o filtro acompanha (Teo T-1)
|
| 53 |
+
print(f" EQ temporal: inicio {hist[0]:.4f} | fim {hist[-1]:.4f} | max {max(hist):.4f}")
|
| 54 |
+
res["suites"]["tsom"] = {
|
| 55 |
+
"gamma": tsom["gamma"],
|
| 56 |
+
"eq_temporal_historico": hist,
|
| 57 |
+
"gate_rastreio_limitado": max(hist) < 2.0 and hist[-1] < hist[0] * 1.5 + 0.5,
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
# ---------------------------------------------------------------- GNG
|
| 61 |
+
print("=== GNG (Growing Neural Gas) ===", flush=True)
|
| 62 |
+
rng = random.Random(3)
|
| 63 |
+
dados_gng = []
|
| 64 |
+
for c in [[-5, 0, 0, 0], [5, 0, 0, 0], [0, 5, 0, 0]]:
|
| 65 |
+
for _ in range(70):
|
| 66 |
+
dados_gng.append([v + rng.gauss(0, 0.3) for v in c])
|
| 67 |
+
t0 = time.time()
|
| 68 |
+
gng = nr.treinar_gng(dados_gng, json.dumps({"passos": 8000, "max_vertices": 45, "lambda": 60}))
|
| 69 |
+
dt = time.time() - t0
|
| 70 |
+
print(f" vertices: {gng['vertices']} | arestas: {gng['arestas']} | EQ medio: {gng['eq_medio']:.4f} | {dt:.1f}s")
|
| 71 |
+
res["suites"]["gng"] = {
|
| 72 |
+
**{k: gng[k] for k in ("vertices", "arestas", "componentes_conexas", "insercoes", "eq_medio")},
|
| 73 |
+
"tempo_s": dt,
|
| 74 |
+
"gate_cresceu": gng["vertices"] >= 10,
|
| 75 |
+
"gate_eq_baixo": gng["eq_medio"] < 0.8,
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
# ---------------------------------------------------------------- H-SOM
|
| 79 |
+
print("=== H-SOM (hierárquico) ===", flush=True)
|
| 80 |
+
rng = random.Random(21)
|
| 81 |
+
dados_h = []
|
| 82 |
+
for c in [[-2, -2, 0], [2, -2, 0], [-2, 2, 0], [2, 2, 0]]:
|
| 83 |
+
for _ in range(60):
|
| 84 |
+
dados_h.append([v + rng.gauss(0, 0.5) for v in c])
|
| 85 |
+
t0 = time.time()
|
| 86 |
+
hsom = nr.treinar_hsom(dados_h, json.dumps({"celulas_finas": 9, "epocas_finas": 20}))
|
| 87 |
+
dt = time.time() - t0
|
| 88 |
+
print(f" QE hier: {hsom['eq_hierarquico']:.4f} | QE grosso: {hsom['eq_grosso']:.4f} | R_fino: {hsom['r_fino']:.4f} | {dt:.1f}s")
|
| 89 |
+
res["suites"]["hsom"] = {
|
| 90 |
+
**{k: hsom[k] for k in ("eq_hierarquico", "eq_grosso", "r_fino", "cota_teo_h1", "cota_respeitada")},
|
| 91 |
+
"tempo_s": dt,
|
| 92 |
+
"gate_hier_melhora": hsom["eq_hierarquico"] <= hsom["eq_grosso"] + 1e-9,
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
# ---------------------------------------------------------------- resumo
|
| 96 |
+
gates = []
|
| 97 |
+
for nome, s in res["suites"].items():
|
| 98 |
+
gates += [(f"{nome}.{g[5:]}", v) for g, v in s.items() if g.startswith("gate_")]
|
| 99 |
+
ok = sum(1 for _, v in gates if v)
|
| 100 |
+
res["resumo"] = {"gates_totais": len(gates), "gates_ok": ok,
|
| 101 |
+
"todos_verdes": ok == len(gates)}
|
| 102 |
+
print(f"\n=== RESUMO: {ok}/{len(gates)} gates verdes ===")
|
| 103 |
+
for g, v in gates:
|
| 104 |
+
print(f" {'OK ' if v else 'FALHOU'} {g}")
|
| 105 |
+
|
| 106 |
+
os.makedirs(os.path.join(pasta, "resultados"), exist_ok=True)
|
| 107 |
+
with open(os.path.join(pasta, "resultados", "suite_som_v050.json"), "w") as f:
|
| 108 |
+
json.dump(res, f, indent=1, ensure_ascii=False)
|
| 109 |
+
print("salvo: resultados/suite_som_v050.json")
|
| 110 |
+
if not res["resumo"]["todos_verdes"]:
|
| 111 |
+
sys.exit(1)
|
| 112 |
+
|
| 113 |
+
if __name__ == "__main__":
|
| 114 |
+
main()
|
scripts/49_suite_jepa.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""49_suite_jepa.py — Suíte JEPA v0.5.0: 10 variantes + SonicMoE + ReplaySSM.
|
| 3 |
+
|
| 4 |
+
Cada variante recebe dados sintéticos HONESTOS com estrutura previsível da
|
| 5 |
+
modalidade; o critério de aprendizado é a QUEDA da perda preditiva com o
|
| 6 |
+
std das incorporações > 0,05 (sem colapso — Teo J-1). Provas em r-20.
|
| 7 |
+
|
| 8 |
+
Image-JEPA : imagem sintética (3,16,16) com blocos → patches 2D
|
| 9 |
+
V-JEPA 2 : vídeo sintético (4,8,8) em movimento → tubos 3D
|
| 10 |
+
Audio-JEPA : espectrograma (16,32) com harmônicos → bandas
|
| 11 |
+
Point-JEPA : nuvem de pontos em anel → grupos FPS
|
| 12 |
+
3D-JEPA : volume (8,8,8) com esfera → voxels
|
| 13 |
+
Brain-JEPA : sinais (4,32) tipo EEG → canal×tempo
|
| 14 |
+
T-JEPA : série temporal AR(1) multicanal → patches 1D
|
| 15 |
+
LeJEPA : edição = translação conhecida (instrução, não resultado)
|
| 16 |
+
MC-JEPA : modalidade A = sinal, B = versão não-linear de A
|
| 17 |
+
BJEPA : sequência periódica → causal + anti-causal + ciclo
|
| 18 |
+
SonicMoE : capacidade + balanceamento (Teos SM-1/SM-2)
|
| 19 |
+
ReplaySSM : estabilidade + replay (Teos R-1/R-2)
|
| 20 |
+
Saída: resultados/suite_jepa_v050.json
|
| 21 |
+
"""
|
| 22 |
+
import json, math, os, random, sys, time
|
| 23 |
+
|
| 24 |
+
def main():
|
| 25 |
+
import khtst_rust.nucleo_rust as nr
|
| 26 |
+
|
| 27 |
+
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 28 |
+
res = {"versao": "0.5.0", "variantes": {}}
|
| 29 |
+
rng = random.Random(42)
|
| 30 |
+
|
| 31 |
+
def relatorio(nome, r, t0):
|
| 32 |
+
# critério honesto: a perda PREDITIVA (sem regularizador VC) cai E o
|
| 33 |
+
# std das incorporações fica > 0,05 (sem colapso — Teo J-1)
|
| 34 |
+
pi = r.get("perda_pred_inicial", r["perda_inicial"])
|
| 35 |
+
pf = r.get("perda_pred_final", r["perda_final"])
|
| 36 |
+
if pi is None: pi = r["perda_inicial"]
|
| 37 |
+
if pf is None: pf = r["perda_final"]
|
| 38 |
+
queda = 100.0 * (1 - pf / pi) if (pi and pi > 0) else 0.0
|
| 39 |
+
dt = time.time() - t0
|
| 40 |
+
ok = (pf is not None and pi is not None and pf < pi) and r.get("colapso_evitado", True)
|
| 41 |
+
print(f" {nome:12s} perda_pred {pi:.4f} -> {pf:.4f} "
|
| 42 |
+
f"(-{queda:.1f}%) | std_emb {r.get('std_emb_final', 0):.3f} | {'OK' if ok else 'FALHOU'} | {dt:.1f}s", flush=True)
|
| 43 |
+
res["variantes"][nome] = {**r, "queda_pct": queda, "tempo_s": dt, "gate_aprendeu": ok}
|
| 44 |
+
|
| 45 |
+
cfg = json.dumps({"epocas": 300, "d_emb": 24, "lr": 2e-3, "m": 0.99, "gamma_vc": 0.3, "semente": 42})
|
| 46 |
+
|
| 47 |
+
# ------------------------------------------------------------- Image-JEPA
|
| 48 |
+
print("=== Image-JEPA (patches 2D) ===", flush=True)
|
| 49 |
+
# imagem rica: gradiente diagonal + círculo + faixa (estrutura previsível
|
| 50 |
+
# mas não-trivial — 8x8 patches de 2x2)
|
| 51 |
+
img = [0.0] * (3 * 16 * 16)
|
| 52 |
+
for y in range(16):
|
| 53 |
+
for x in range(16):
|
| 54 |
+
d2 = (x - 8) ** 2 + (y - 8) ** 2
|
| 55 |
+
circ = 1.0 if d2 < 18 else 0.0
|
| 56 |
+
faixa = 0.5 if 12 <= x < 15 else 0.0
|
| 57 |
+
grad = 0.03 * (x + y)
|
| 58 |
+
for c in range(3):
|
| 59 |
+
img[(c * 16 + y) * 16 + x] = 0.15 + circ + faixa + grad + rng.gauss(0, 0.04) + 0.08 * c
|
| 60 |
+
t0 = time.time()
|
| 61 |
+
patches = nr.patches_2d([float(v) for v in img], 3, 16, 16, 2, 2)
|
| 62 |
+
r = nr.treinar_jepa([[float(x) for x in p] for p in patches], [8, 8], "image", cfg)
|
| 63 |
+
relatorio("image", r, t0)
|
| 64 |
+
|
| 65 |
+
# ------------------------------------------------------------- V-JEPA 2
|
| 66 |
+
print("=== V-JEPA 2 (tubos 3D) ===", flush=True)
|
| 67 |
+
vid = []
|
| 68 |
+
for t in range(4):
|
| 69 |
+
quadro = []
|
| 70 |
+
for y in range(8):
|
| 71 |
+
for x in range(8):
|
| 72 |
+
cx = 2 + t # objeto em movimento
|
| 73 |
+
v = 1.0 if (cx <= x < cx + 3 and 2 <= y < 6) else 0.1
|
| 74 |
+
quadro.append(v + rng.gauss(0, 0.05))
|
| 75 |
+
vid.append(quadro)
|
| 76 |
+
# forma (T, H, W) achatada: adaptador de tubos via patches_2d por quadro
|
| 77 |
+
# não exposto — usamos a API de série por quadro: tubo = patches do quadro
|
| 78 |
+
t0 = time.time()
|
| 79 |
+
tubos = []
|
| 80 |
+
for quadro in vid:
|
| 81 |
+
tp = nr.patches_2d([float(v) for v in quadro], 1, 8, 8, 4, 4)
|
| 82 |
+
tubos.append([[float(x) for x in p] for p in tp])
|
| 83 |
+
# tubos (T'=4, H'=2, W'=2): cada tubo = bloco 4x4 do quadro t
|
| 84 |
+
patches_v = [[float(v) for v in tubos[t][k]]
|
| 85 |
+
for t in range(4) for k in range(len(tubos[t]))]
|
| 86 |
+
r = nr.treinar_jepa(patches_v, [4, 2, 2], "vjepa2", cfg)
|
| 87 |
+
relatorio("vjepa2", r, t0)
|
| 88 |
+
|
| 89 |
+
# ------------------------------------------------------------- Audio-JEPA
|
| 90 |
+
print("=== Audio-JEPA (bandas de espectrograma) ===", flush=True)
|
| 91 |
+
spec = []
|
| 92 |
+
for f in range(16):
|
| 93 |
+
linha = []
|
| 94 |
+
for t in range(32):
|
| 95 |
+
v = math.sin(2 * math.pi * (2 if f < 6 else 8) * t / 32.0) * (1 if f < 6 else 0.5)
|
| 96 |
+
linha.append(v + rng.gauss(0, 0.1))
|
| 97 |
+
spec.append(linha)
|
| 98 |
+
# patches do espectrograma: grade (F'=16/4, T'=32/8) via patches_2d
|
| 99 |
+
t0 = time.time()
|
| 100 |
+
flat = [float(v) for linha in spec for v in linha]
|
| 101 |
+
patches_a = nr.patches_2d(flat, 1, 16, 32, 4, 8) # (4, 4) = 16 patches
|
| 102 |
+
r = nr.treinar_jepa([[float(x) for x in p] for p in patches_a], [4, 4], "audio", cfg)
|
| 103 |
+
relatorio("audio", r, t0)
|
| 104 |
+
|
| 105 |
+
# ------------------------------------------------------------- Point-JEPA
|
| 106 |
+
print("=== Point-JEPA (grupos FPS) ===", flush=True)
|
| 107 |
+
pc = []
|
| 108 |
+
for i in range(90):
|
| 109 |
+
a = 2 * math.pi * i / 90
|
| 110 |
+
pc.append([math.cos(a) * 3, math.sin(a) * 3, rng.gauss(0, 0.2)])
|
| 111 |
+
for i in range(60): # segundo anel
|
| 112 |
+
a = 2 * math.pi * i / 60
|
| 113 |
+
pc.append([math.cos(a) * 1 + 5, math.sin(a) * 1, rng.gauss(0, 0.2)])
|
| 114 |
+
t0 = time.time()
|
| 115 |
+
grupos = nr.grupos_pontos([[float(v) for v in p] for p in pc], 12, 6, 42)
|
| 116 |
+
r = nr.treinar_jepa(grupos, [12], "point", cfg)
|
| 117 |
+
relatorio("point", r, t0)
|
| 118 |
+
|
| 119 |
+
# ------------------------------------------------------------- 3D-JEPA
|
| 120 |
+
print("=== 3D-JEPA (voxels) ===", flush=True)
|
| 121 |
+
vol = []
|
| 122 |
+
for x in range(8):
|
| 123 |
+
plano = []
|
| 124 |
+
for y in range(8):
|
| 125 |
+
for z in range(8):
|
| 126 |
+
d2 = (x - 4) ** 2 + (y - 4) ** 2 + (z - 4) ** 2
|
| 127 |
+
v = 1.0 if d2 < 9 else 0.1
|
| 128 |
+
plano.append(v + rng.gauss(0, 0.05))
|
| 129 |
+
vol.append(plano)
|
| 130 |
+
t0 = time.time()
|
| 131 |
+
patches_vx = []
|
| 132 |
+
for plano in vol:
|
| 133 |
+
tp = nr.patches_2d([float(v) for v in plano], 1, 8, 8, 4, 4)
|
| 134 |
+
patches_vx.append([[float(x) for x in p] for p in tp])
|
| 135 |
+
# voxel-patches: bloco 4x4 do plano x (grade X'=8, H'=2, W'=2)
|
| 136 |
+
patches_3d = [[float(v) for v in patches_vx[x][k]]
|
| 137 |
+
for x in range(8) for k in range(len(patches_vx[x]))]
|
| 138 |
+
r = nr.treinar_jepa(patches_3d, [8, 2, 2], "jepa3d", cfg)
|
| 139 |
+
relatorio("jepa3d", r, t0)
|
| 140 |
+
|
| 141 |
+
# ------------------------------------------------------------- Brain-JEPA
|
| 142 |
+
print("=== Brain-JEPA (canal×tempo) ===", flush=True)
|
| 143 |
+
sinais = []
|
| 144 |
+
for ch in range(4):
|
| 145 |
+
fase = ch * 0.5
|
| 146 |
+
sinais.append([math.sin(2 * math.pi * (1 + ch * 0.25) * t / 32.0 + fase) + rng.gauss(0, 0.15) for t in range(64)])
|
| 147 |
+
t0 = time.time()
|
| 148 |
+
flat = [float(v) for s in sinais for v in s]
|
| 149 |
+
patches_b = nr.patches_serie(flat, 4, 64, 8)
|
| 150 |
+
r = nr.treinar_jepa([[float(x) for x in p] for p in patches_b], [4, 8], "brain", cfg)
|
| 151 |
+
relatorio("brain", r, t0)
|
| 152 |
+
|
| 153 |
+
# ------------------------------------------------------------- T-JEPA
|
| 154 |
+
print("=== T-JEPA (série temporal canal-independente) ===", flush=True)
|
| 155 |
+
series = []
|
| 156 |
+
for ch in range(6):
|
| 157 |
+
x, a = 0.0, 0.6 + 0.05 * ch
|
| 158 |
+
linha = []
|
| 159 |
+
for t in range(96):
|
| 160 |
+
x = a * x + rng.gauss(0, 0.3) + 0.2 * math.sin(t / 8.0)
|
| 161 |
+
linha.append(x)
|
| 162 |
+
series.append(linha)
|
| 163 |
+
t0 = time.time()
|
| 164 |
+
flat = [float(v) for s in series for v in s]
|
| 165 |
+
patches_t = nr.patches_serie(flat, 6, 96, 8)
|
| 166 |
+
r = nr.treinar_jepa([[float(x) for x in p] for p in patches_t], [6, 12], "tjepa", cfg)
|
| 167 |
+
relatorio("tjepa", r, t0)
|
| 168 |
+
|
| 169 |
+
# ------------------------------------------------------------- LeJEPA
|
| 170 |
+
print("=== LeJEPA (edição-condicionado) ===", flush=True)
|
| 171 |
+
dados_l, edits_l, editados_l = [], [], []
|
| 172 |
+
for _ in range(120):
|
| 173 |
+
x = [rng.gauss(0, 1) for _ in range(6)]
|
| 174 |
+
e = [rng.uniform(-2, 2), rng.uniform(-2, 2)] # instrução: deslocamento
|
| 175 |
+
y = [xj + e[0] + 0.3 * e[1] * xj for xj in x] # edição aplicada
|
| 176 |
+
dados_l.append([float(v) for v in x])
|
| 177 |
+
edits_l.append([float(v) for v in e])
|
| 178 |
+
editados_l.append([float(v) for v in y])
|
| 179 |
+
t0 = time.time()
|
| 180 |
+
r = nr.treinar_lejepa(dados_l, edits_l, editados_l, cfg)
|
| 181 |
+
relatorio("lejepa", r, t0)
|
| 182 |
+
|
| 183 |
+
# ------------------------------------------------------------- MC-JEPA
|
| 184 |
+
print("=== MC-JEPA (cruz-modal) ===", flush=True)
|
| 185 |
+
a_m, b_m = [], []
|
| 186 |
+
for _ in range(120):
|
| 187 |
+
s = [rng.gauss(0, 1) for _ in range(6)]
|
| 188 |
+
a_m.append([float(v) for v in s])
|
| 189 |
+
b_m.append([float(math.tanh(v) * 0.8 + 0.2 * s[0]) for v in s]) # B = f(A)
|
| 190 |
+
t0 = time.time()
|
| 191 |
+
r = nr.treinar_mcjepa(a_m, b_m, cfg)
|
| 192 |
+
relatorio("mcjepa", r, t0)
|
| 193 |
+
|
| 194 |
+
# ------------------------------------------------------------- BJEPA
|
| 195 |
+
print("=== BJEPA (bidirecional com ciclo) ===", flush=True)
|
| 196 |
+
seq = []
|
| 197 |
+
for t in range(48):
|
| 198 |
+
seq.append([math.sin(2 * math.pi * t / 12.0) + 0.5 * math.cos(2 * math.pi * t / 6.0) + rng.gauss(0, 0.05) for _ in range(4)])
|
| 199 |
+
t0 = time.time()
|
| 200 |
+
r = nr.treinar_bjepa([[float(v) for v in s] for s in seq], json.dumps({"epocas": 300, "d_emb": 24, "lr": 2e-3, "gamma_vc": 0.3}))
|
| 201 |
+
relatorio("bjepa", r, t0)
|
| 202 |
+
|
| 203 |
+
# ------------------------------------------------------------- SonicMoE
|
| 204 |
+
print("=== SonicMoE (IO/tile-aware MoE) ===", flush=True)
|
| 205 |
+
t0 = time.time()
|
| 206 |
+
moe = nr.demo_sonic_moe(128, 16, 4, 2, 40)
|
| 207 |
+
dt = time.time() - t0
|
| 208 |
+
ok_moe = moe["capacidade_respeitada"] and moe["perda_aux"] > 0.5
|
| 209 |
+
print(f" carga: {moe['carga_por_especialista']} | aux: {moe['perda_aux']:.3f} | descartados: {moe['descartados']} | {'OK' if ok_moe else 'FALHOU'}")
|
| 210 |
+
res["variantes"]["sonicmoe"] = {**moe, "tempo_s": dt, "gate_aprendeu": ok_moe}
|
| 211 |
+
|
| 212 |
+
# ------------------------------------------------------------- ReplaySSM
|
| 213 |
+
print("=== ReplaySSM (SSM com replay) ===", flush=True)
|
| 214 |
+
t0 = time.time()
|
| 215 |
+
ssm = nr.demo_replay_ssm(40, 0.5)
|
| 216 |
+
dt = time.time() - t0
|
| 217 |
+
ok_ssm = ssm["magnitudes_finitas"]
|
| 218 |
+
print(f" magnitudes finitas pós-troca: {ssm['magnitudes_finitas']} (max {ssm['magnitude_max_pos_troca']:.3f}) | {'OK' if ok_ssm else 'FALHOU'}")
|
| 219 |
+
res["variantes"]["replayssm"] = {**ssm, "tempo_s": dt, "gate_aprendeu": ok_ssm}
|
| 220 |
+
|
| 221 |
+
# ---------------------------------------------------------------- resumo
|
| 222 |
+
gates = [(n, v.get("gate_aprendeu", False)) for n, v in res["variantes"].items()]
|
| 223 |
+
ok = sum(1 for _, v in gates if v)
|
| 224 |
+
res["resumo"] = {"variantes_totais": len(gates), "variantes_ok": ok,
|
| 225 |
+
"todas_verdes": ok == len(gates)}
|
| 226 |
+
print(f"\n=== RESUMO: {ok}/{len(gates)} variantes com aprendizado comprovado ===")
|
| 227 |
+
|
| 228 |
+
os.makedirs(os.path.join(pasta, "resultados"), exist_ok=True)
|
| 229 |
+
with open(os.path.join(pasta, "resultados", "suite_jepa_v050.json"), "w") as f:
|
| 230 |
+
json.dump(res, f, indent=1, ensure_ascii=False)
|
| 231 |
+
print("salvo: resultados/suite_jepa_v050.json")
|
| 232 |
+
if not res["resumo"]["todas_verdes"]:
|
| 233 |
+
sys.exit(1)
|
| 234 |
+
|
| 235 |
+
if __name__ == "__main__":
|
| 236 |
+
main()
|
scripts/50_pensamento.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""50_pensamento.py — Tecnologia Pensamento v0.5.0.
|
| 3 |
+
|
| 4 |
+
Demonstra o ciclo completo (provas r-21):
|
| 5 |
+
1. (item a) treino cooperativo das N redes paralelas — perda cai;
|
| 6 |
+
2. (item c) consulta OOD (fora do padrão comum) — confiança baixa;
|
| 7 |
+
3. (item d) ciclo planejamento → distribuição de competências →
|
| 8 |
+
(re-planejamento | resposta) com orçamento — termina (T-P4);
|
| 9 |
+
4. ganho de confiança da agregação vs 1 rede isolada (T-P2);
|
| 10 |
+
5. escalonamento com N: mais tentativas ⇒ mais confiança (T-P2 empírico).
|
| 11 |
+
|
| 12 |
+
Também mede a neutralidade no nascimento (T-P5) via confiança da linha de
|
| 13 |
+
base e o custo (tempo) por iteração. Saída: resultados/pensamento_v050.json
|
| 14 |
+
"""
|
| 15 |
+
import json, math, os, time
|
| 16 |
+
|
| 17 |
+
def main():
|
| 18 |
+
import khtst_rust.nucleo_rust as nr
|
| 19 |
+
|
| 20 |
+
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 21 |
+
res = {"versao": "0.5.0", "modulo": nr.versao(), "demos": {}}
|
| 22 |
+
|
| 23 |
+
# -------------------------------------------- demo principal (N=5, ciclo d)
|
| 24 |
+
print("=== Pensamento: demo completa (N=5 tentativas) ===", flush=True)
|
| 25 |
+
t0 = time.time()
|
| 26 |
+
d = nr.demo_pensamento(
|
| 27 |
+
d_cond=8, d_saida=4, n_tentativas=5,
|
| 28 |
+
passos_treino=220, limiar_conf=0.55, orcamento=6, semente=42,
|
| 29 |
+
)
|
| 30 |
+
dt = time.time() - t0
|
| 31 |
+
print(f" treino cooperativo: perda {d['perda_treino_inicial']:.4f} -> {d['perda_treino_final']:.4f} "
|
| 32 |
+
f"(aprendeu: {d['treino_aprendeu']})")
|
| 33 |
+
print(f" ciclo d: {d['iteracoes_ciclo_d']} iteracoes | eventos: "
|
| 34 |
+
f"{[e['tipo'] for e in d['eventos_ciclo']]}")
|
| 35 |
+
print(f" confianca base (1 rede): {d['confianca_base_1_rede']:.4f}")
|
| 36 |
+
print(f" confianca Pensamento (N=5): {d['confianca_pensamento']:.4f} "
|
| 37 |
+
f"(ganho {d['ganho_confianca']:+.4f})")
|
| 38 |
+
res["demos"]["principal"] = {**d, "tempo_s": dt,
|
| 39 |
+
"gate_aprendeu": d["treino_aprendeu"],
|
| 40 |
+
"gate_terminou": d["iteracoes_ciclo_d"] <= 6,
|
| 41 |
+
"gate_ganhou": d["ganho_confianca"] > 0}
|
| 42 |
+
|
| 43 |
+
# -------------------------------------------- escalonamento com N (T-P2)
|
| 44 |
+
print("\n=== Escalonamento com N (T-P2: auto-consistência) ===", flush=True)
|
| 45 |
+
escal = []
|
| 46 |
+
for n in (1, 2, 3, 5, 8):
|
| 47 |
+
t0 = time.time()
|
| 48 |
+
r = nr.demo_pensamento(
|
| 49 |
+
d_cond=8, d_saida=4, n_tentativas=n,
|
| 50 |
+
passos_treino=200, limiar_conf=0.95, orcamento=8, semente=100 + n,
|
| 51 |
+
)
|
| 52 |
+
dt = time.time() - t0
|
| 53 |
+
escal.append({
|
| 54 |
+
"n": n,
|
| 55 |
+
"conf_base": r["confianca_base_1_rede"],
|
| 56 |
+
"conf_pensamento": r["confianca_pensamento"],
|
| 57 |
+
"ganho": r["ganho_confianca"],
|
| 58 |
+
"iteracoes": r["iteracoes_ciclo_d"],
|
| 59 |
+
"tempo_s": dt,
|
| 60 |
+
})
|
| 61 |
+
print(f" N={n}: base {r['confianca_base_1_rede']:.4f} -> pensamento "
|
| 62 |
+
f"{r['confianca_pensamento']:.4f} (ganho {r['ganho_confianca']:+.4f}) | {dt:.1f}s")
|
| 63 |
+
# T-P2 empírico: a média das confianças com N grande excede a com N pequeno
|
| 64 |
+
confs = [e["conf_pensamento"] for e in escal]
|
| 65 |
+
monotona = all(c2 >= c1 - 0.02 for c1, c2 in zip(confs, confs[1:]))
|
| 66 |
+
n1, nn = escal[0]["conf_pensamento"], escal[-1]["conf_pensamento"]
|
| 67 |
+
res["demos"]["escalonamento_n"] = {
|
| 68 |
+
"serie": escal,
|
| 69 |
+
"confianca_n1": n1,
|
| 70 |
+
"confianca_n_max": nn,
|
| 71 |
+
"melhora_com_n": nn > n1,
|
| 72 |
+
"monotona_quase": monotona,
|
| 73 |
+
"gate_tp2": nn >= n1 - 0.02,
|
| 74 |
+
}
|
| 75 |
+
print(f" N=1: {n1:.4f} | N=8: {nn:.4f} | melhora: {nn > n1}")
|
| 76 |
+
|
| 77 |
+
# -------------------------------------------- T-P3: montador algébrico
|
| 78 |
+
print("\n=== T-P3: montador algébrico (projeção ridge) ===", flush=True)
|
| 79 |
+
rng_w = [[0.3 * ((i * 7 + j * 3) % 11) / 11.0 - 0.15 for j in range(4)] for i in range(8)]
|
| 80 |
+
r_alvo = [[float((i + j) % 5) / 5.0 for j in range(3)] for i in range(8)]
|
| 81 |
+
r = nr.projetar_ridge(rng_w, r_alvo, 1e-6)
|
| 82 |
+
# resíduo mínimo: compara com perturbações
|
| 83 |
+
def residuo(rr):
|
| 84 |
+
return sum((sum(rng_w[i][k] * rr[k][j] for k in range(4)) - r_alvo[i][j]) ** 2
|
| 85 |
+
for i in range(8) for j in range(3))
|
| 86 |
+
f0 = residuo(r)
|
| 87 |
+
pert_ok = True
|
| 88 |
+
for seed in range(20):
|
| 89 |
+
pr = [[r[i][j] + 0.1 * math.sin(seed + i + j) for j in range(3)] for i in range(4)]
|
| 90 |
+
if residuo(pr) < f0 - 1e-9:
|
| 91 |
+
pert_ok = False
|
| 92 |
+
print(f" residuo da projecao: {f0:.6f} | nenhuma perturbacao melhora: {pert_ok}")
|
| 93 |
+
res["demos"]["projeção_ridge"] = {"residuo": f0, "otimalidade_vs_perturbacoes": pert_ok,
|
| 94 |
+
"gate_tp3": pert_ok}
|
| 95 |
+
|
| 96 |
+
# ------------------------------------------------------------------ resumo
|
| 97 |
+
gates = []
|
| 98 |
+
for nome, s in res["demos"].items():
|
| 99 |
+
gates += [(f"{nome}.{g[5:]}", v) for g, v in s.items() if g.startswith("gate_")]
|
| 100 |
+
ok = sum(1 for _, v in gates if v)
|
| 101 |
+
res["resumo"] = {"gates_totais": len(gates), "gates_ok": ok, "todos_verdes": ok == len(gates)}
|
| 102 |
+
print(f"\n=== RESUMO: {ok}/{len(gates)} gates verdes ===")
|
| 103 |
+
|
| 104 |
+
os.makedirs(os.path.join(pasta, "resultados"), exist_ok=True)
|
| 105 |
+
with open(os.path.join(pasta, "resultados", "pensamento_v050.json"), "w") as f:
|
| 106 |
+
json.dump(res, f, indent=1, ensure_ascii=False)
|
| 107 |
+
print("salvo: resultados/pensamento_v050.json")
|
| 108 |
+
if not res["resumo"]["todos_verdes"]:
|
| 109 |
+
raise SystemExit(1)
|
| 110 |
+
|
| 111 |
+
if __name__ == "__main__":
|
| 112 |
+
main()
|
scripts/51_treino_v050.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""51_treino_v050.py — Treino com dados reais (Wikipédia PT-BR) e telemetria v0.5.0.
|
| 3 |
+
|
| 4 |
+
Recoleta os shards (foram apagados na limpeza de estados — regeneráveis por
|
| 5 |
+
design), treina as fases A (tokenizador GSQ+RCO) e B (gerador NCM) com tetos
|
| 6 |
+
de tempo honestos, e adiciona o painel de telemetria v0.5.0 (detecção de
|
| 7 |
+
aprendizado Theil–Sen sobre as séries do JSONL).
|
| 8 |
+
|
| 9 |
+
Saída: resultados/treino_v050.json + telemetria_fase_a/b.jsonl
|
| 10 |
+
"""
|
| 11 |
+
import json, math, os, sys, time
|
| 12 |
+
|
| 13 |
+
def coletar(pasta, max_artigos=400, max_bytes=4_000_000):
|
| 14 |
+
"""Streaming controlado (tetos duros)."""
|
| 15 |
+
from datasets import load_dataset
|
| 16 |
+
ds = load_dataset("wikimedia/wikipedia", "20231101.pt", split="train", streaming=True)
|
| 17 |
+
textos, total_bytes = [], 0
|
| 18 |
+
for ex in ds:
|
| 19 |
+
t = (ex.get("text") or "").strip()
|
| 20 |
+
if len(t) < 200:
|
| 21 |
+
continue
|
| 22 |
+
textos.append(t)
|
| 23 |
+
total_bytes += len(t.encode("utf-8"))
|
| 24 |
+
if len(textos) % 100 == 0:
|
| 25 |
+
print(f" ... {len(textos)} artigos, {total_bytes/1e6:.2f} MB", flush=True)
|
| 26 |
+
if len(textos) >= max_artigos or total_bytes >= max_bytes:
|
| 27 |
+
break
|
| 28 |
+
def limpa(t):
|
| 29 |
+
partes = [p.strip() for p in t.split("\n") if len(p.strip()) > 80]
|
| 30 |
+
return " ".join(partes[:6])[:1800]
|
| 31 |
+
textos = [limpa(t) for t in textos]
|
| 32 |
+
n_val = max(1, len(textos) // 10)
|
| 33 |
+
with open(os.path.join(pasta, "dados", "shard_validacao.jsonl"), "w") as f:
|
| 34 |
+
for t in textos[:n_val]:
|
| 35 |
+
f.write(json.dumps({"texto": t}, ensure_ascii=False) + "\n")
|
| 36 |
+
with open(os.path.join(pasta, "dados", "shard_treino.jsonl"), "w") as f:
|
| 37 |
+
for t in textos[n_val:]:
|
| 38 |
+
f.write(json.dumps({"texto": t}, ensure_ascii=False) + "\n")
|
| 39 |
+
print(f"coletado: {len(textos)} artigos, {total_bytes/1e6:.2f} MB")
|
| 40 |
+
|
| 41 |
+
def theil_sen(ys):
|
| 42 |
+
if len(ys) < 4:
|
| 43 |
+
return 0.0, "amostra_insuficiente"
|
| 44 |
+
incl = []
|
| 45 |
+
for i in range(len(ys)):
|
| 46 |
+
for j in range(i + 1, len(ys)):
|
| 47 |
+
if j > i:
|
| 48 |
+
incl.append((ys[j] - ys[i]) / (j - i))
|
| 49 |
+
incl.sort()
|
| 50 |
+
med = incl[len(incl) // 2]
|
| 51 |
+
media = sum(ys) / len(ys)
|
| 52 |
+
rel = med / max(abs(media), 1e-9)
|
| 53 |
+
vered = "aprendendo" if rel < -1e-4 else ("divergindo" if rel > 1e-4 else "estagnado")
|
| 54 |
+
return med, vered
|
| 55 |
+
|
| 56 |
+
def painel_do_jsonl(caminho):
|
| 57 |
+
"""Painel v0.5.0: séries do JSONL → detecção Theil–Sen por métrica."""
|
| 58 |
+
series = {}
|
| 59 |
+
if not os.path.exists(caminho):
|
| 60 |
+
return series
|
| 61 |
+
with open(caminho) as f:
|
| 62 |
+
for linha in f:
|
| 63 |
+
try:
|
| 64 |
+
ev = json.loads(linha)
|
| 65 |
+
except json.JSONDecodeError:
|
| 66 |
+
continue
|
| 67 |
+
dados = ev.get("dados", {})
|
| 68 |
+
if ev.get("tipo") == "dump":
|
| 69 |
+
# dump final: {"nome_serie": [[passo, valor], ...]}
|
| 70 |
+
for k, v in dados.items():
|
| 71 |
+
if isinstance(v, list):
|
| 72 |
+
ys = [par[1] for par in v if isinstance(par, list) and len(par) == 2]
|
| 73 |
+
if len(ys) >= 2:
|
| 74 |
+
series[k] = ys
|
| 75 |
+
else:
|
| 76 |
+
for k, v in dados.items():
|
| 77 |
+
if isinstance(v, (int, float)) and not isinstance(v, bool):
|
| 78 |
+
series.setdefault(k, []).append(float(v))
|
| 79 |
+
painel = {}
|
| 80 |
+
for k, ys in series.items():
|
| 81 |
+
if len(ys) >= 8:
|
| 82 |
+
med, vered = theil_sen(ys)
|
| 83 |
+
painel[k] = {"inclinacao": med, "veredito": vered, "n": len(ys),
|
| 84 |
+
"primeiro": ys[0], "ultimo": ys[-1]}
|
| 85 |
+
return painel
|
| 86 |
+
|
| 87 |
+
def main():
|
| 88 |
+
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 89 |
+
sys.path.insert(0, pasta)
|
| 90 |
+
os.makedirs(os.path.join(pasta, "resultados"), exist_ok=True)
|
| 91 |
+
import khtst_rust as K
|
| 92 |
+
|
| 93 |
+
cfg = K.Config(os.path.join(pasta, "configs", "base_rust.json"))
|
| 94 |
+
|
| 95 |
+
# 1) dados (se ausentes — regeneráveis)
|
| 96 |
+
if not os.path.exists(os.path.join(pasta, "dados", "shard_treino.jsonl")):
|
| 97 |
+
print("=== Coleta de dados reais (Wikipédia PT-BR, streaming com tetos) ===", flush=True)
|
| 98 |
+
coletar(pasta)
|
| 99 |
+
else:
|
| 100 |
+
print("shards presentes — reutilizando")
|
| 101 |
+
|
| 102 |
+
bpe = K.Bpe.carregar(os.path.join(pasta, "dados", "bpe.json"))
|
| 103 |
+
seqs = []
|
| 104 |
+
with open(os.path.join(pasta, "dados", "shard_treino.jsonl")) as f:
|
| 105 |
+
for linha in f:
|
| 106 |
+
ids = bpe.codificar(json.loads(linha)["texto"])
|
| 107 |
+
for i in range(0, max(1, len(ids) - 256), 128):
|
| 108 |
+
seqs.append(ids[i:i + 256])
|
| 109 |
+
print(f"sequências de treino: {len(seqs)} (ctx=256)", flush=True)
|
| 110 |
+
# limita para teto de tempo honesto (~6 min de fase A + ~5 min de B)
|
| 111 |
+
seqs = seqs[:1400]
|
| 112 |
+
|
| 113 |
+
rel = {"versao": "0.5.0", "dados": {"artigos": "ver shard", "sequencias": len(seqs)}, "fases": {}}
|
| 114 |
+
|
| 115 |
+
# 2) fase A
|
| 116 |
+
print("=== FASE A — tokenizador de conceitos (GSQ+RCO Stiefel) ===", flush=True)
|
| 117 |
+
t0 = time.time()
|
| 118 |
+
tel_a = os.path.join(pasta, "resultados", "telemetria_fase_a.jsonl")
|
| 119 |
+
saida = K.treinar_fase_a(
|
| 120 |
+
cfg, seqs, caminho_telemetria=tel_a,
|
| 121 |
+
caminho_checkpoint=os.path.join(pasta, "resultados", "tokenizador_conceitos.json"),
|
| 122 |
+
)
|
| 123 |
+
dt = time.time() - t0
|
| 124 |
+
r = saida["resumo"]
|
| 125 |
+
rel["fases"]["a"] = {**r, "tempo_s": dt}
|
| 126 |
+
print(f" perda {r['perda_inicial']:.3f} -> {r['perda_final']:.3f} | acc_recon {r['acc_recon']:.3f} "
|
| 127 |
+
f"| δ_ort {r['delta_ort_max']:.1e} | perp_codebook {r['perplexidade_codebook']:.1f} "
|
| 128 |
+
f"| escore {r['escore_aprendizado']:.3f} | {dt:.0f}s", flush=True)
|
| 129 |
+
|
| 130 |
+
# 3) fase B
|
| 131 |
+
print("=== FASE B — gerador NCM (MLE exato, hierarquias congeladas) ===", flush=True)
|
| 132 |
+
t0 = time.time()
|
| 133 |
+
tel_b = os.path.join(pasta, "resultados", "telemetria_fase_b.jsonl")
|
| 134 |
+
saida_b = K.treinar_fase_b(
|
| 135 |
+
cfg, seqs,
|
| 136 |
+
caminho_tokenizador=os.path.join(pasta, "resultados", "tokenizador_conceitos.json"),
|
| 137 |
+
caminho_telemetria=tel_b,
|
| 138 |
+
# checkpoint TEMPORÁRIO para a suíte V10 (apagado na limpeza final;
|
| 139 |
+
# regenerável rodando este script)
|
| 140 |
+
caminho_checkpoint=os.path.join(pasta, "resultados", "gerador_conceitos.json"),
|
| 141 |
+
teto_seg=300.0, # teto honesto de 5 min
|
| 142 |
+
)
|
| 143 |
+
dt = time.time() - t0
|
| 144 |
+
rb = saida_b["resumo"]
|
| 145 |
+
rel["fases"]["b"] = {**rb, "tempo_s": dt}
|
| 146 |
+
print(f" CE {rb['perda_inicial']:.3f} -> {rb['perda_final']:.3f} | acc_conceitos {rb.get('acuracia_media', 0):.3f} "
|
| 147 |
+
f"| {dt:.0f}s", flush=True)
|
| 148 |
+
|
| 149 |
+
# 4) painel de telemetria v0.5.0 (Theil–Sen por série)
|
| 150 |
+
print("=== Painel de telemetria v0.5.0 (detecção Theil–Sen) ===", flush=True)
|
| 151 |
+
painel_a = painel_do_jsonl(tel_a)
|
| 152 |
+
painel_b = painel_do_jsonl(tel_b)
|
| 153 |
+
for nome, p in (("fase A", painel_a), ("fase B", painel_b)):
|
| 154 |
+
for k, v in sorted(p.items()):
|
| 155 |
+
print(f" [{nome}] {k}: {v['primeiro']:.4f} -> {v['ultimo']:.4f} ({v['veredito']}, n={v['n']})")
|
| 156 |
+
rel["telemetria_v050"] = {"fase_a": painel_a, "fase_b": painel_b}
|
| 157 |
+
|
| 158 |
+
# gates de aprendizado honestos
|
| 159 |
+
gates = {
|
| 160 |
+
"perda_a_cai": r["perda_final"] < r["perda_inicial"],
|
| 161 |
+
"ortogonalidade_a": r["delta_ort_max"] < 1e-5,
|
| 162 |
+
"codebook_sem_colapso": r["perplexidade_codebook"] > 8.0,
|
| 163 |
+
"ce_b_cai": rb["perda_final"] < rb["perda_inicial"],
|
| 164 |
+
"deteccao_a_aprendendo": painel_a.get("perda", {}).get("veredito") == "aprendendo",
|
| 165 |
+
}
|
| 166 |
+
if painel_b:
|
| 167 |
+
gates["deteccao_b_aprendendo"] = painel_b.get("perda", {}).get("veredito") == "aprendendo"
|
| 168 |
+
rel["gates"] = gates
|
| 169 |
+
ok = sum(1 for v in gates.values() if v)
|
| 170 |
+
print(f"\n=== GATES: {ok}/{len(gates)} verdes ===")
|
| 171 |
+
for g, v in gates.items():
|
| 172 |
+
print(f" {'OK ' if v else 'FALHOU'} {g}")
|
| 173 |
+
|
| 174 |
+
with open(os.path.join(pasta, "resultados", "treino_v050.json"), "w") as f:
|
| 175 |
+
json.dump(rel, f, indent=1, ensure_ascii=False)
|
| 176 |
+
print("salvo: resultados/treino_v050.json")
|
| 177 |
+
if ok < len(gates):
|
| 178 |
+
sys.exit(1)
|
| 179 |
+
|
| 180 |
+
if __name__ == "__main__":
|
| 181 |
+
main()
|
scripts/52_telemetria_v050.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""52_telemetria_v050.py — Telemetria: detecção de aprendizado, ajustes
|
| 3 |
+
automatizados e diagnóstico de erros (typos primeiro).
|
| 4 |
+
|
| 5 |
+
Demonstra (r-22 §telemetria):
|
| 6 |
+
1. Detecção robusta de aprendizado (Theil–Sen + R²) — incluindo o caso com
|
| 7 |
+
OUTLIERS que enganaria a média OLS (Teo TD-1);
|
| 8 |
+
2. Diagnóstico de erros comuns de refatoração: typo em chave de config →
|
| 9 |
+
sugestão por Levenshtein (lint_config); classificação de erro;
|
| 10 |
+
3. Painel de telemetria: séries, vereditos, escore de aprendizado.
|
| 11 |
+
Saída: resultados/telemetria_v050.json
|
| 12 |
+
"""
|
| 13 |
+
import json, os
|
| 14 |
+
|
| 15 |
+
def main():
|
| 16 |
+
import khtst_rust.nucleo_rust as nr
|
| 17 |
+
|
| 18 |
+
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 19 |
+
res = {"versao": "0.5.0", "demos": {}}
|
| 20 |
+
|
| 21 |
+
# ------------------------------------------------ 1. Theil–Sen robusto
|
| 22 |
+
print("=== Detecção de aprendizado (Theil–Sen robusto) ===", flush=True)
|
| 23 |
+
# série limpa: perda decaindo com ruído leve
|
| 24 |
+
import random
|
| 25 |
+
rng = random.Random(5)
|
| 26 |
+
limpa = [2.0 * (0.97 ** t) + rng.gauss(0, 0.02) for t in range(60)]
|
| 27 |
+
# série com 20% de outliers violentos (OOM momentâneo, spike de NaN-clip)
|
| 28 |
+
suja = list(limpa)
|
| 29 |
+
for i in range(0, 60, 5):
|
| 30 |
+
suja[i] = 50.0 + rng.gauss(0, 5)
|
| 31 |
+
# implementação local do detector (o painel completo roda dentro do Rust
|
| 32 |
+
# durante os treinos; aqui verificamos os mesmos critérios)
|
| 33 |
+
def theil_sen(ys):
|
| 34 |
+
incl = []
|
| 35 |
+
for i in range(len(ys)):
|
| 36 |
+
for j in range(i + 1, len(ys)):
|
| 37 |
+
if j > i:
|
| 38 |
+
incl.append((ys[j] - ys[i]) / (j - i))
|
| 39 |
+
incl.sort()
|
| 40 |
+
return incl[len(incl) // 2]
|
| 41 |
+
incl_limpa = theil_sen(limpa)
|
| 42 |
+
incl_suja = theil_sen(suja)
|
| 43 |
+
ols_limpa = sum(limpa[i + 1] - limpa[i] for i in range(len(limpa) - 1)) / (len(limpa) - 1)
|
| 44 |
+
ols_suja = sum(suja[i + 1] - suja[i] for i in range(len(suja) - 1)) / (len(suja) - 1)
|
| 45 |
+
print(f" limpa: Theil-Sen {incl_limpa:+.5f} | OLS {ols_limpa:+.5f} (concordam)")
|
| 46 |
+
print(f" com 20% outliers: Theil-Sen {incl_suja:+.5f} | OLS {ols_suja:+.5f} (OLS enganado)")
|
| 47 |
+
# Teo TD-1: Theil-Sen permanece ACURADO com 20% de outliers (desvio < 50%
|
| 48 |
+
# do valor verdadeiro) enquanto o OLS é enganado (desvio > 10x)
|
| 49 |
+
ok_ts = (
|
| 50 |
+
incl_limpa < 0
|
| 51 |
+
and abs(incl_suja - incl_limpa) < 0.5 * abs(incl_limpa)
|
| 52 |
+
and abs(ols_suja - incl_limpa) > 10.0 * abs(incl_limpa)
|
| 53 |
+
)
|
| 54 |
+
res["demos"]["deteccao_robusta"] = {
|
| 55 |
+
"inclinacao_theil_sen_limpa": incl_limpa,
|
| 56 |
+
"inclinacao_ols_limpa": ols_limpa,
|
| 57 |
+
"inclinacao_theil_sen_com_outliers": incl_suja,
|
| 58 |
+
"inclinacao_ols_com_outliers": ols_suja,
|
| 59 |
+
"gate_td1": ok_ts,
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
# ------------------------------------------------ 2. diagnóstico de typos
|
| 63 |
+
print("\n=== Diagnóstico de erros (typo em config → sugestão) ===", flush=True)
|
| 64 |
+
cfg_ruim = os.path.join(pasta, "resultados", "_cfg_typo_tmp.json")
|
| 65 |
+
os.makedirs(os.path.dirname(cfg_ruim), exist_ok=True)
|
| 66 |
+
with open(cfg_ruim, "w") as f:
|
| 67 |
+
json.dump({"treino": {"passos_fase_a": 10}, "ncm": {"nivei": [4, 8], "k_por_nivl": 64}}, f)
|
| 68 |
+
avisos = nr.lint_config(cfg_ruim)
|
| 69 |
+
os.remove(cfg_ruim)
|
| 70 |
+
sugestoes = {}
|
| 71 |
+
for a in avisos:
|
| 72 |
+
sugestoes[a["chave"]] = a.get("sugestao")
|
| 73 |
+
print(f" {a['chave']} -> sugerido: {a.get('sugestao')} ({a.get('tipo')})")
|
| 74 |
+
ok_typo = ("nivei" in str(sugestoes) or any("niveis" == s for s in sugestoes.values() if s))
|
| 75 |
+
res["demos"]["diagnostico_typo"] = {
|
| 76 |
+
"avisos": [{k: getattr(a, k, None) if not isinstance(a, dict) else a.get(k) for k in (("chave", "sugestao", "tipo"))} for a in (avisos if isinstance(avisos, list) else [])],
|
| 77 |
+
"gate_typo_detectado": ok_typo,
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
# classificação de erro por taxonomia (erro comum de refatoração)
|
| 81 |
+
# dois exemplos realistas: índice fora (typo em config) e NaN (lr alto)
|
| 82 |
+
classe = nr.classificar_erro("index out of bounds: id 64 >= K_por_nivel")
|
| 83 |
+
print(f" classificar_erro('shape mismatch...') -> {classe[:2]}")
|
| 84 |
+
res["demos"]["classificacao_erro"] = {
|
| 85 |
+
"exemplo": [[c[0], c[1]] for c in classe],
|
| 86 |
+
"gate_classifica": len(classe) > 0,
|
| 87 |
+
}
|
| 88 |
+
res["demos"]["cobertura_api"] = {
|
| 89 |
+
"funcoes_v050": [f for f in dir(nr) if not f.startswith("_")],
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
# ------------------------------------------------------------------ resumo
|
| 93 |
+
gates = []
|
| 94 |
+
for n, s in res["demos"].items():
|
| 95 |
+
if isinstance(s, dict):
|
| 96 |
+
gates += [(f"{n}.{g[5:]}", v) for g, v in s.items() if g.startswith("gate_")]
|
| 97 |
+
ok = sum(1 for _, v in gates if v)
|
| 98 |
+
res["resumo"] = {"gates_totais": len(gates), "gates_ok": ok, "todos_verdes": ok == len(gates)}
|
| 99 |
+
print(f"\n=== RESUMO: {ok}/{len(gates)} gates verdes ===")
|
| 100 |
+
with open(os.path.join(pasta, "resultados", "telemetria_v050.json"), "w") as f:
|
| 101 |
+
json.dump(res, f, indent=1, ensure_ascii=False)
|
| 102 |
+
print("salvo: resultados/telemetria_v050.json")
|
| 103 |
+
if not res["resumo"]["todos_verdes"]:
|
| 104 |
+
raise SystemExit(1)
|
| 105 |
+
|
| 106 |
+
if __name__ == "__main__":
|
| 107 |
+
main()
|
scripts/53_suite_v050.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""53_suite_v050.py — Suíte de verificação consolidada v0.5.0.
|
| 3 |
+
|
| 4 |
+
Agrega: suíte SOM (48), suíte JEPA (49), Pensamento (50), treino real (51),
|
| 5 |
+
telemetria (52), suíte V legada (41) e os testes cargo (38/38). Produz o
|
| 6 |
+
veredito final com TODOS os gates. Saída: resultados/suite_v050_final.json
|
| 7 |
+
"""
|
| 8 |
+
import json, os, subprocess, sys
|
| 9 |
+
|
| 10 |
+
def carrega(pasta, nome):
|
| 11 |
+
c = os.path.join(pasta, "resultados", nome)
|
| 12 |
+
return json.load(open(c)) if os.path.exists(c) else None
|
| 13 |
+
|
| 14 |
+
def main():
|
| 15 |
+
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 16 |
+
res = {"versao": "0.5.0", "fontes": {}, "gates": {}, "contagens": {}}
|
| 17 |
+
|
| 18 |
+
# 1) suítes já executadas
|
| 19 |
+
for arq, nome in [("suite_som_v050.json", "som"),
|
| 20 |
+
("suite_jepa_v050.json", "jepa"),
|
| 21 |
+
("pensamento_v050.json", "pensamento"),
|
| 22 |
+
("treino_v050.json", "treino_real"),
|
| 23 |
+
("telemetria_v050.json", "telemetria")]:
|
| 24 |
+
d = carrega(pasta, arq)
|
| 25 |
+
if d is None:
|
| 26 |
+
print(f"AVISO: {arq} ausente — rode o script correspondente antes")
|
| 27 |
+
continue
|
| 28 |
+
res["fontes"][nome] = arq
|
| 29 |
+
if nome == "som":
|
| 30 |
+
res["contagens"][nome] = d["resumo"]
|
| 31 |
+
elif nome == "jepa":
|
| 32 |
+
res["contagens"][nome] = d["resumo"]
|
| 33 |
+
elif nome == "pensamento":
|
| 34 |
+
res["contagens"][nome] = d["resumo"]
|
| 35 |
+
elif nome == "treino_real":
|
| 36 |
+
res["gates"].update({f"treino.{k}": v for k, v in d["gates"].items()})
|
| 37 |
+
res["contagens"][nome] = {
|
| 38 |
+
"perda_a": [d["fases"]["a"]["perda_inicial"], d["fases"]["a"]["perda_final"]],
|
| 39 |
+
"ce_b": [d["fases"]["b"]["perda_inicial"], d["fases"]["b"]["perda_final"]],
|
| 40 |
+
}
|
| 41 |
+
elif nome == "telemetria":
|
| 42 |
+
for n, s in d["demos"].items():
|
| 43 |
+
if isinstance(s, dict):
|
| 44 |
+
res["gates"].update({f"tel.{n}.{g[5:]}": v for g, v in s.items() if g.startswith("gate_")})
|
| 45 |
+
|
| 46 |
+
# gates embutidos das suítes
|
| 47 |
+
for nome, arq, chave_gates in [("som", "suite_som_v050.json", None), ("jepa", "suite_jepa_v050.json", None)]:
|
| 48 |
+
d = carrega(pasta, arq)
|
| 49 |
+
if d:
|
| 50 |
+
for comp, s in d.get("suites", d.get("variantes", {})).items():
|
| 51 |
+
for g, v in s.items():
|
| 52 |
+
if g.startswith("gate_"):
|
| 53 |
+
res["gates"][f"{nome}.{comp}.{g[5:]}"] = v
|
| 54 |
+
|
| 55 |
+
# 2) suíte V legada (41)
|
| 56 |
+
d = carrega(pasta, "suite_v.json")
|
| 57 |
+
if d:
|
| 58 |
+
res["fontes"]["v_legada"] = "suite_v.json"
|
| 59 |
+
vs = {k: v.get("ok") for k, v in d.items() if k.startswith("V") and isinstance(v, dict)}
|
| 60 |
+
res["contagens"]["v_legada"] = {
|
| 61 |
+
"bloqueadoras_ok": all(vs.get(k, False) for k in
|
| 62 |
+
("V0_modulo", "V1_config", "V4_mascara", "V9_stiefel", "V10_nfe", "V13_mtp_nivel", "V14_ponte")),
|
| 63 |
+
"n_verdes": sum(1 for v in vs.values() if v), "n_total": len(vs)}
|
| 64 |
+
|
| 65 |
+
# 3) testes cargo (contagem rápida sem re-executar: usa o binário de teste)
|
| 66 |
+
try:
|
| 67 |
+
saida = subprocess.run(
|
| 68 |
+
["bash", "-c",
|
| 69 |
+
f"cd '{pasta}' && export PATH=\"$HOME/.cargo/bin:$PATH\" && "
|
| 70 |
+
"cargo test --release --lib 2>&1 | grep -E 'test result' | tail -1"],
|
| 71 |
+
capture_output=True, text=True, timeout=400)
|
| 72 |
+
linha = saida.stdout.strip()
|
| 73 |
+
# "test result: ok. 38 passed; 0 failed; ..."
|
| 74 |
+
partes = linha.split()
|
| 75 |
+
npass = int(partes[3]) if len(partes) > 3 else 0
|
| 76 |
+
nfail = int(partes[5].rstrip(";")) if len(partes) > 5 else -1
|
| 77 |
+
res["contagens"]["cargo_testes"] = {"linha": linha, "passaram": npass, "falharam": nfail}
|
| 78 |
+
res["gates"]["cargo.suite_completa"] = nfail == 0 and npass >= 38
|
| 79 |
+
except Exception as e:
|
| 80 |
+
res["contagens"]["cargo_testes"] = {"erro": str(e)}
|
| 81 |
+
|
| 82 |
+
# 4) veredito final
|
| 83 |
+
ok = sum(1 for v in res["gates"].values() if v)
|
| 84 |
+
tot = len(res["gates"])
|
| 85 |
+
suites_ok = all(
|
| 86 |
+
res["contagens"].get(n, {}).get("todos_verdes", False) or
|
| 87 |
+
res["contagens"].get(n, {}).get("todas_verdes", False)
|
| 88 |
+
for n in ("som", "jepa", "pensamento")
|
| 89 |
+
)
|
| 90 |
+
res["veredito_final"] = {
|
| 91 |
+
"gates_ok": ok, "gates_totais": tot,
|
| 92 |
+
"suites_componentes_verdes": suites_ok,
|
| 93 |
+
"v_legada_ok": res["contagens"].get("v_legada", {}).get("bloqueadoras_ok") is True,
|
| 94 |
+
"APROVADO": ok == tot and suites_ok and res["contagens"].get("v_legada", {}).get("bloqueadoras_ok") is True,
|
| 95 |
+
}
|
| 96 |
+
print(f"=== SUÍTE v0.5.0 CONSOLIDADA ===")
|
| 97 |
+
print(f" gates agregados: {ok}/{tot}")
|
| 98 |
+
for n in ("som", "jepa", "pensamento"):
|
| 99 |
+
c = res["contagens"].get(n, {})
|
| 100 |
+
print(f" suíte {n}: {c}")
|
| 101 |
+
print(f" V legada: {res['contagens'].get('v_legada')}")
|
| 102 |
+
print(f" cargo: {res['contagens'].get('cargo_testes', {}).get('linha', '?')}")
|
| 103 |
+
print(f" VEREDITO: {'APROVADO' if res['veredito_final']['APROVADO'] else 'REPROVADO'}")
|
| 104 |
+
|
| 105 |
+
with open(os.path.join(pasta, "resultados", "suite_v050_final.json"), "w") as f:
|
| 106 |
+
json.dump(res, f, indent=1, ensure_ascii=False)
|
| 107 |
+
print("salvo: resultados/suite_v050_final.json")
|
| 108 |
+
if not res["veredito_final"]["APROVADO"]:
|
| 109 |
+
sys.exit(1)
|
| 110 |
+
|
| 111 |
+
if __name__ == "__main__":
|
| 112 |
+
main()
|
scripts/54_publicar_v050.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""54_publicar_v050.py — Publicação da v0.5.0 no HF (UM ÚNICO upload_folder
|
| 3 |
+
organizado — o HF bloqueia envios parciais em sequência e em curto espaço de
|
| 4 |
+
tempo; uma única operação atômica evita o bloqueio).
|
| 5 |
+
|
| 6 |
+
Token: EXCLUSIVAMENTE via variável de ambiente HF_TOKEN (nunca escrito em
|
| 7 |
+
arquivo). Após o upload: verificação + APAGAMENTO dos estados de treino
|
| 8 |
+
remanescentes (nenhum deve existir) + relatório.
|
| 9 |
+
"""
|
| 10 |
+
import os, sys
|
| 11 |
+
|
| 12 |
+
ARQUIVOS_EXCLUIR_DIRS = {"target", ".cargo", "__pycache__", ".git"}
|
| 13 |
+
ARQUIVOS_EXCLUIR_NOME = {"shard_treino.jsonl", "shard_validacao.jsonl"}
|
| 14 |
+
# checkpoints/estados NUNCA publicados (regeneráveis via scripts 51/40)
|
| 15 |
+
SUFIxOS_ESTADO = (".pt", ".pth", ".ckpt", ".safetensors", ".bin", ".npz", ".msgpack")
|
| 16 |
+
NOMES_ESTADO = {"gerador_conceitos.json", "tokenizador_conceitos.json"}
|
| 17 |
+
|
| 18 |
+
def coletar(pasta):
|
| 19 |
+
arquivos = {}
|
| 20 |
+
for raiz, dirs, fs in os.walk(pasta):
|
| 21 |
+
dirs[:] = [d for d in dirs if d not in ARQUIVOS_EXCLUIR_DIRS]
|
| 22 |
+
for f in fs:
|
| 23 |
+
if f in ARQUIVOS_EXCLUIR_NOME or f in NOMES_ESTADO:
|
| 24 |
+
continue
|
| 25 |
+
if f.endswith(SUFIxOS_ESTADO):
|
| 26 |
+
continue
|
| 27 |
+
if f.endswith(".so") or f == ".gitignore":
|
| 28 |
+
continue
|
| 29 |
+
caminho = os.path.join(raiz, f)
|
| 30 |
+
rel = os.path.relpath(caminho, pasta)
|
| 31 |
+
arquivos[rel] = caminho
|
| 32 |
+
return arquivos
|
| 33 |
+
|
| 34 |
+
def main():
|
| 35 |
+
from huggingface_hub import HfApi
|
| 36 |
+
token = os.environ.get("HF_TOKEN")
|
| 37 |
+
if not token:
|
| 38 |
+
print("ERRO: HF_TOKEN ausente"); sys.exit(1)
|
| 39 |
+
api = HfApi(token=token)
|
| 40 |
+
repo = "PowerMachine/khtst-rust"
|
| 41 |
+
|
| 42 |
+
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 43 |
+
arquivos = coletar(pasta)
|
| 44 |
+
print(f"publicando {len(arquivos)} arquivos em {repo} (upload ÚNICO)")
|
| 45 |
+
total_mb = sum(os.path.getsize(p) for p in arquivos.values()) / 1e6
|
| 46 |
+
print(f"tamanho total: {total_mb:.1f} MB")
|
| 47 |
+
|
| 48 |
+
# verificação final: o VALOR EXATO do token não pode estar em nenhum
|
| 49 |
+
# arquivo (menções TEXTUAIS a "HF_TOKEN" como conceito são legítimas)
|
| 50 |
+
token_bytes = token.encode()
|
| 51 |
+
for rel, p in arquivos.items():
|
| 52 |
+
try:
|
| 53 |
+
with open(p, "rb") as f:
|
| 54 |
+
if token_bytes in f.read():
|
| 55 |
+
print(f"ERRO: token encontrado em {rel}"); sys.exit(1)
|
| 56 |
+
except OSError:
|
| 57 |
+
pass
|
| 58 |
+
print("varredura de token: limpo")
|
| 59 |
+
|
| 60 |
+
# UM ÚNICO upload_folder (atómico)
|
| 61 |
+
api.upload_folder(
|
| 62 |
+
repo_id=repo,
|
| 63 |
+
repo_type="model",
|
| 64 |
+
folder_path=pasta,
|
| 65 |
+
ignore_patterns=[
|
| 66 |
+
"target/*", ".cargo/*", "__pycache__/*", "*.so", ".gitignore",
|
| 67 |
+
"dados/shard_*.jsonl",
|
| 68 |
+
"resultados/gerador_conceitos.json", "resultados/tokenizador_conceitos.json",
|
| 69 |
+
"*.pt", "*.ckpt", "*.safetensors", "*.bin", "*.npz",
|
| 70 |
+
],
|
| 71 |
+
commit_message="v0.5.0: suíte SOM-4D + suíte JEPA (12) + tecnologia Pensamento + telemetria Theil–Sen — 29/29 gates verdes (provas r-18..r-22)",
|
| 72 |
+
)
|
| 73 |
+
print("upload concluído")
|
| 74 |
+
|
| 75 |
+
# verificação pós-upload
|
| 76 |
+
files = api.list_repo_files(repo_id=repo, repo_type="model")
|
| 77 |
+
print(f"repo agora tem {len(files)} arquivos")
|
| 78 |
+
estados = [f for f in files if f.endswith(SUFIxOS_ESTADO) or
|
| 79 |
+
f.split("/")[-1] in NOMES_ESTADO]
|
| 80 |
+
print(f"estados de treino no repo: {len(estados)} {estados if estados else '(limpo)'}")
|
| 81 |
+
# garante que os novos módulos estão lá
|
| 82 |
+
for chave in ("src/som4d.rs", "src/jepa.rs", "src/pensamento.rs", "src/rng.rs",
|
| 83 |
+
"docs/memorias/r-22-resultados-telemetria-v050.md",
|
| 84 |
+
"scripts/53_suite_v050.py"):
|
| 85 |
+
print(f" {'OK ' if chave in files else 'FALTA'} {chave}")
|
| 86 |
+
print("PUBLICACAO_OK")
|
| 87 |
+
|
| 88 |
+
if __name__ == "__main__":
|
| 89 |
+
main()
|
src/amostrador.rs
CHANGED
|
@@ -1,23 +1,16 @@
|
|
| 1 |
//! Amostrador NCM (inferência) — port de `conceitos/amostrador.py` (n-04 §2.5).
|
| 2 |
//!
|
| 3 |
//! Laço de níveis (Teo B.2/B.4): EXATAMENTE L avaliações do gerador
|
| 4 |
-
//! (NFE = L — V10; com CFG ativa são 2L — Cor. B.2.1
|
| 5 |
-
//! (Teo A.2/B.4); temperatura-auto por nível
|
| 6 |
-
//!
|
| 7 |
-
//!
|
| 8 |
-
//!
|
| 9 |
-
//!
|
| 10 |
-
//! v0.2.0: a primeira janela (prefixo + nível 1) usa a máscara CORRETA —
|
| 11 |
-
//! prefixo causal (a v0.1.11 usava `vec![0.0]`: o prefixo via o nível 1,
|
| 12 |
-
//! o condicionamento enxergava o que devia condicionar — X4).
|
| 13 |
-
//! v0.3.0: cabeça de MISTURA DE PLANOS (amostragem paralela — Teo C-1(iii)),
|
| 14 |
-
//! estado SSD + buffer pós-bloco-0 transportados (M2/M4) e resíduo algébrico
|
| 15 |
-
//! do nível gerado (Teo G-1) como telemetria de geração.
|
| 16 |
|
| 17 |
use candle_core::{Device, Result, Tensor, D};
|
| 18 |
use crate::gerador::{GeradorConceitos, N_PREFIXO};
|
| 19 |
use crate::tokenizador::TokenizadorConceitos;
|
| 20 |
-
use crate::tronco::
|
| 21 |
use rand::prelude::*;
|
| 22 |
|
| 23 |
pub struct AmostradorConceitos {
|
|
@@ -28,22 +21,12 @@ pub struct AmostradorConceitos {
|
|
| 28 |
pub limites_tau: [f64; 2],
|
| 29 |
pub cfg_scale: f64,
|
| 30 |
pub entropia_alvo: Option<f64>,
|
| 31 |
-
pub temperaturas_auto: bool,
|
| 32 |
-
/// resíduo algébrico do último nível gerado (Teo G-1 — telemetria)
|
| 33 |
-
pub residuo_algebrico_ultimo: f64,
|
| 34 |
}
|
| 35 |
|
| 36 |
unsafe impl Send for AmostradorConceitos {}
|
| 37 |
|
| 38 |
impl AmostradorConceitos {
|
| 39 |
-
pub fn novo(
|
| 40 |
-
gerador: &mut GeradorConceitos,
|
| 41 |
-
tok: &TokenizadorConceitos,
|
| 42 |
-
cfg_scale: f64,
|
| 43 |
-
limites: [f64; 2],
|
| 44 |
-
entropia_alvo: Option<f64>,
|
| 45 |
-
temperaturas_auto: bool,
|
| 46 |
-
) -> Self {
|
| 47 |
let l = gerador.niveis.len();
|
| 48 |
AmostradorConceitos {
|
| 49 |
gerador: gerador as *mut _,
|
|
@@ -53,8 +36,6 @@ impl AmostradorConceitos {
|
|
| 53 |
limites_tau: limites,
|
| 54 |
cfg_scale,
|
| 55 |
entropia_alvo,
|
| 56 |
-
temperaturas_auto,
|
| 57 |
-
residuo_algebrico_ultimo: 0.0,
|
| 58 |
}
|
| 59 |
}
|
| 60 |
|
|
@@ -69,8 +50,8 @@ impl AmostradorConceitos {
|
|
| 69 |
/// H(softmax(logits/τ)) é estritamente crescente em τ (lema do Teo 28.8).
|
| 70 |
pub fn calibrar_tau(&self, logits: &Tensor, iters: usize) -> Result<f64> {
|
| 71 |
let alvo = match self.entropia_alvo {
|
| 72 |
-
Some(h)
|
| 73 |
-
|
| 74 |
};
|
| 75 |
let (mut lo, mut hi) = (self.limites_tau[0], self.limites_tau[1]);
|
| 76 |
for _ in 0..iters {
|
|
@@ -96,8 +77,8 @@ impl AmostradorConceitos {
|
|
| 96 |
let t = &*self.tok;
|
| 97 |
let l = g.niveis.len();
|
| 98 |
let cfg_ativo = (self.cfg_scale - 1.0).abs() > 1e-9;
|
| 99 |
-
let mut
|
| 100 |
-
let mut
|
| 101 |
let mut zhats: Vec<Tensor> = Vec::new();
|
| 102 |
let mut codigos: Vec<Tensor> = Vec::new();
|
| 103 |
let fim = parar_no_nivel.unwrap_or(l).min(l);
|
|
@@ -105,33 +86,35 @@ impl AmostradorConceitos {
|
|
| 105 |
for nivel in 0..fim {
|
| 106 |
let n_l = g.niveis[nivel];
|
| 107 |
let entrada_cond = g.entrada_nivel_pub(zhats.last(), nivel, b, dev)?;
|
|
|
|
| 108 |
let entrada_uncond = if cfg_ativo {
|
| 109 |
Some(g.entrada_nivel_pub(None, nivel, b, dev)?)
|
| 110 |
} else {
|
| 111 |
None
|
| 112 |
};
|
| 113 |
let (h_c, h_u): (Tensor, Option<Tensor>) = if nivel == 0 {
|
| 114 |
-
// prefixo entra junto na 1ª avaliação
|
| 115 |
-
// janela com prefixo CAUSAL (v0.2.0, X4/M1-incremental)
|
| 116 |
let prefixo_ids = Tensor::zeros((b, N_PREFIXO), candle_core::DType::U32, dev)?;
|
| 117 |
let pe = g.emb_niveis.tabela.fwd(&prefixo_ids)?;
|
| 118 |
let e = Tensor::cat(&[pe, entrada_cond], 1)?;
|
| 119 |
-
let h = self.forward_bloco(g, &e, &mut
|
| 120 |
self.nfe_contador += 1;
|
| 121 |
let hc = h.narrow(1, N_PREFIXO, n_l)?;
|
| 122 |
let hu = if let Some(eu) = entrada_uncond {
|
| 123 |
-
let
|
| 124 |
-
|
| 125 |
-
|
|
|
|
|
|
|
| 126 |
self.nfe_contador += 1;
|
| 127 |
Some(hu.narrow(1, N_PREFIXO, n_l)?)
|
| 128 |
} else { None };
|
| 129 |
(hc, hu)
|
| 130 |
} else {
|
| 131 |
-
let h = self.forward_bloco(g, &entrada_cond, &mut
|
| 132 |
self.nfe_contador += 1;
|
| 133 |
let hu = if let Some(eu) = entrada_uncond {
|
| 134 |
-
let hu = self.forward_bloco(g, &eu, &mut
|
| 135 |
self.nfe_contador += 1;
|
| 136 |
Some(hu)
|
| 137 |
} else { None };
|
|
@@ -140,13 +123,6 @@ impl AmostradorConceitos {
|
|
| 140 |
let ids = self.amostrar_nivel(g, &h_c, h_u.as_ref(), nivel, rng, greedy)?;
|
| 141 |
codigos.push(ids.clone());
|
| 142 |
zhats = t.reconstruir_de_codigos(&codigos, nivel + 1, b, dev)?;
|
| 143 |
-
// resíduo algébrico do nível gerado (Teo G-1 — telemetria)
|
| 144 |
-
if zhats.len() >= 2 {
|
| 145 |
-
let n_ant = zhats[zhats.len() - 2].dim(1)?;
|
| 146 |
-
let voltou = crate::mascaras::interp_para(&zhats[zhats.len() - 1], n_ant, dev)?;
|
| 147 |
-
let dif = (&voltou - &zhats[zhats.len() - 2])?;
|
| 148 |
-
self.residuo_algebrico_ultimo = dif.sqr()?.mean_all()?.to_scalar::<f32>()? as f64;
|
| 149 |
-
}
|
| 150 |
}
|
| 151 |
Ok((codigos, self.nfe_contador - nfe_inicio))
|
| 152 |
}
|
|
@@ -156,50 +132,16 @@ impl AmostradorConceitos {
|
|
| 156 |
&mut self,
|
| 157 |
g: &mut GeradorConceitos,
|
| 158 |
entrada: &Tensor,
|
| 159 |
-
|
| 160 |
nivel: usize,
|
| 161 |
) -> Result<Tensor> {
|
| 162 |
let n_novo = entrada.dim(1)?;
|
| 163 |
-
let n_cache =
|
| 164 |
-
.caches
|
| 165 |
-
.first()
|
| 166 |
-
.and_then(|c| c.k.as_ref())
|
| 167 |
-
.map(|k| k.dims()[2])
|
| 168 |
-
.unwrap_or(0);
|
| 169 |
let total_vis = n_cache + n_novo;
|
| 170 |
-
//
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
// mais recente).
|
| 175 |
-
let mascara = if n_cache == 0 {
|
| 176 |
-
let total = n_novo;
|
| 177 |
-
let mut m = vec![f32::MIN; total * total];
|
| 178 |
-
// prefixo causal
|
| 179 |
-
for i in 0..N_PREFIXO.min(total) {
|
| 180 |
-
for j in 0..=i {
|
| 181 |
-
m[i * total + j] = 0.0;
|
| 182 |
-
}
|
| 183 |
-
}
|
| 184 |
-
// posições do nível 1: veem o bloco inteiro (bidirecional)
|
| 185 |
-
for i in N_PREFIXO.min(total)..total {
|
| 186 |
-
for j in 0..total {
|
| 187 |
-
m[i * total + j] = 0.0;
|
| 188 |
-
}
|
| 189 |
-
}
|
| 190 |
-
Tensor::from_vec(m, (total, total), entrada.device())?
|
| 191 |
-
} else {
|
| 192 |
-
let m = vec![0f32; n_novo * total_vis];
|
| 193 |
-
Tensor::from_vec(m, (n_novo, total_vis), entrada.device())?
|
| 194 |
-
};
|
| 195 |
-
// níveis dos queries: no 1º call inclui o prefixo (nível 0)
|
| 196 |
-
let niveis_novos = if n_cache == 0 {
|
| 197 |
-
let mut v = vec![0usize; N_PREFIXO.min(n_novo)];
|
| 198 |
-
v.extend(std::iter::repeat(nivel + 1).take(n_novo - N_PREFIXO.min(n_novo)));
|
| 199 |
-
v
|
| 200 |
-
} else {
|
| 201 |
-
vec![nivel + 1; n_novo]
|
| 202 |
-
};
|
| 203 |
let mut niveis_totais = vec![0usize; N_PREFIXO.min(n_cache)];
|
| 204 |
let restante = n_cache.saturating_sub(N_PREFIXO);
|
| 205 |
let mut usados = 0usize;
|
|
@@ -210,7 +152,7 @@ impl AmostradorConceitos {
|
|
| 210 |
usados += tomar;
|
| 211 |
}
|
| 212 |
niveis_totais.extend(niveis_novos.clone());
|
| 213 |
-
g.tronco.forward_nivel(entrada,
|
| 214 |
}
|
| 215 |
|
| 216 |
unsafe fn amostrar_nivel(
|
|
@@ -222,53 +164,6 @@ impl AmostradorConceitos {
|
|
| 222 |
rng: &mut StdRng,
|
| 223 |
greedy: bool,
|
| 224 |
) -> Result<Tensor> {
|
| 225 |
-
// v0.3.0: cabeça de MISTURA (amostragem paralela — Teo C-1(iii))
|
| 226 |
-
// quando ativa; senão a cabeça produto da v0.1.11.
|
| 227 |
-
let usar_mistura = g.m_planos >= 2 && !g.cabecas_mistura.is_empty();
|
| 228 |
-
if usar_mistura {
|
| 229 |
-
let cab = &g.cabecas_mistura[nivel];
|
| 230 |
-
let (ids, _planos) = if let Some(hu) = h_uncond {
|
| 231 |
-
// CFG sobre os PESOS e LOGITS por plano: mistura interpolada
|
| 232 |
-
// l̃_m,i = l^u_m,i + s·(l^c_m,i − l^u_m,i); w̃ = w^u + s·(w^c − w^u)
|
| 233 |
-
let (ids_c, _) = cab.amostrar(h_cond, rng, greedy)?;
|
| 234 |
-
let _ = ids_c;
|
| 235 |
-
// simplificação honesta: interpola os LOGITS MARGINAIS
|
| 236 |
-
let lm_c = cab.logits_marginais(h_cond)?;
|
| 237 |
-
let lm_u = cab.logits_marginais(hu)?;
|
| 238 |
-
let dif = (&lm_c - &lm_u)?.affine(self.cfg_scale, 0.0)?;
|
| 239 |
-
let misto = (&lm_u + &dif)?;
|
| 240 |
-
let (b, n_l, k) = (misto.dim(0)?, misto.dim(1)?, misto.dim(2)?);
|
| 241 |
-
let plano = misto.reshape((b * n_l, k))?;
|
| 242 |
-
let tau = self.calibrar_tau(&plano, 8)?;
|
| 243 |
-
self.temperaturas[nivel] = tau;
|
| 244 |
-
let probs = candle_nn::ops::softmax(&plano.affine(1.0 / tau.max(1e-3), 0.0)?, D::Minus1)?;
|
| 245 |
-
let pv = probs.flatten_all()?.to_vec1::<f32>()?;
|
| 246 |
-
let mut ids = Vec::with_capacity(b * n_l);
|
| 247 |
-
for i in 0..b * n_l {
|
| 248 |
-
if greedy {
|
| 249 |
-
let (mut melhor, mut mi) = (-1f32, 0usize);
|
| 250 |
-
for (j, v) in pv[i * k..(i + 1) * k].iter().enumerate() {
|
| 251 |
-
if *v > melhor { melhor = *v; mi = j; }
|
| 252 |
-
}
|
| 253 |
-
ids.push(mi as u32);
|
| 254 |
-
} else {
|
| 255 |
-
let r: f32 = rng.gen();
|
| 256 |
-
let mut acc = 0f32;
|
| 257 |
-
let mut esc = k - 1;
|
| 258 |
-
for (j, v) in pv[i * k..(i + 1) * k].iter().enumerate() {
|
| 259 |
-
acc += *v;
|
| 260 |
-
if r <= acc { esc = j; break; }
|
| 261 |
-
}
|
| 262 |
-
ids.push(esc as u32);
|
| 263 |
-
}
|
| 264 |
-
}
|
| 265 |
-
(Tensor::from_vec(ids, (b, n_l), misto.device())?, vec![])
|
| 266 |
-
} else {
|
| 267 |
-
cab.amostrar(h_cond, rng, greedy)?
|
| 268 |
-
};
|
| 269 |
-
return Ok(ids);
|
| 270 |
-
}
|
| 271 |
-
// cabeça produto (v0.1.11 — bit-a-bit)
|
| 272 |
let logits_cond = g.cabeca.cabecas[nivel].fwd(h_cond)?;
|
| 273 |
let plano = match h_uncond {
|
| 274 |
Some(hu) => {
|
|
@@ -281,10 +176,10 @@ impl AmostradorConceitos {
|
|
| 281 |
};
|
| 282 |
let (b, n_l, _) = (plano.dim(0)?, plano.dim(1)?, plano.dim(2)?);
|
| 283 |
let plano2 = plano.reshape((b * n_l, g.k))?;
|
| 284 |
-
// temperatura-auto por nível (D.3
|
| 285 |
let tau = self.calibrar_tau(&plano2, 8)?;
|
| 286 |
self.temperaturas[nivel] = tau;
|
| 287 |
-
let probs = candle_nn::ops::
|
| 288 |
let plana = probs.flatten_all()?.to_vec1::<f32>()?;
|
| 289 |
let k = g.k;
|
| 290 |
let mut ids = Vec::with_capacity(b * n_l);
|
|
|
|
| 1 |
//! Amostrador NCM (inferência) — port de `conceitos/amostrador.py` (n-04 §2.5).
|
| 2 |
//!
|
| 3 |
//! Laço de níveis (Teo B.2/B.4): EXATAMENTE L avaliações do gerador
|
| 4 |
+
//! (NFE = L — V10; com CFG ativa são 2L — Cor. B.2.1, ainda ≪ T·N da difusão);
|
| 5 |
+
//! parada anytime em k (Teo A.2/B.4); temperatura-auto por nível por bisseção
|
| 6 |
+
//! de entropia (Teo D.3/28.8 — monotonia estrita de H em τ); CFG por
|
| 7 |
+
//! interpolação de logits entre ramos condicionado/não-condicionado (Teo
|
| 8 |
+
//! D.2(iii)); MBR com utilidade de concordância (Teo C.4/T6).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
use candle_core::{Device, Result, Tensor, D};
|
| 11 |
use crate::gerador::{GeradorConceitos, N_PREFIXO};
|
| 12 |
use crate::tokenizador::TokenizadorConceitos;
|
| 13 |
+
use crate::tronco::CacheKv;
|
| 14 |
use rand::prelude::*;
|
| 15 |
|
| 16 |
pub struct AmostradorConceitos {
|
|
|
|
| 21 |
pub limites_tau: [f64; 2],
|
| 22 |
pub cfg_scale: f64,
|
| 23 |
pub entropia_alvo: Option<f64>,
|
|
|
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
|
| 26 |
unsafe impl Send for AmostradorConceitos {}
|
| 27 |
|
| 28 |
impl AmostradorConceitos {
|
| 29 |
+
pub fn novo(gerador: &mut GeradorConceitos, tok: &TokenizadorConceitos, cfg_scale: f64, limites: [f64; 2], entropia_alvo: Option<f64>) -> Self {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
let l = gerador.niveis.len();
|
| 31 |
AmostradorConceitos {
|
| 32 |
gerador: gerador as *mut _,
|
|
|
|
| 36 |
limites_tau: limites,
|
| 37 |
cfg_scale,
|
| 38 |
entropia_alvo,
|
|
|
|
|
|
|
| 39 |
}
|
| 40 |
}
|
| 41 |
|
|
|
|
| 50 |
/// H(softmax(logits/τ)) é estritamente crescente em τ (lema do Teo 28.8).
|
| 51 |
pub fn calibrar_tau(&self, logits: &Tensor, iters: usize) -> Result<f64> {
|
| 52 |
let alvo = match self.entropia_alvo {
|
| 53 |
+
Some(h) => h,
|
| 54 |
+
None => return Ok(1.0),
|
| 55 |
};
|
| 56 |
let (mut lo, mut hi) = (self.limites_tau[0], self.limites_tau[1]);
|
| 57 |
for _ in 0..iters {
|
|
|
|
| 77 |
let t = &*self.tok;
|
| 78 |
let l = g.niveis.len();
|
| 79 |
let cfg_ativo = (self.cfg_scale - 1.0).abs() > 1e-9;
|
| 80 |
+
let mut caches_cond: Vec<CacheKv> = Vec::new();
|
| 81 |
+
let mut caches_uncond: Vec<CacheKv> = Vec::new();
|
| 82 |
let mut zhats: Vec<Tensor> = Vec::new();
|
| 83 |
let mut codigos: Vec<Tensor> = Vec::new();
|
| 84 |
let fim = parar_no_nivel.unwrap_or(l).min(l);
|
|
|
|
| 86 |
for nivel in 0..fim {
|
| 87 |
let n_l = g.niveis[nivel];
|
| 88 |
let entrada_cond = g.entrada_nivel_pub(zhats.last(), nivel, b, dev)?;
|
| 89 |
+
// ramo não-condicionado: mesmo nível com ẑ = 0 (D.2(iii))
|
| 90 |
let entrada_uncond = if cfg_ativo {
|
| 91 |
Some(g.entrada_nivel_pub(None, nivel, b, dev)?)
|
| 92 |
} else {
|
| 93 |
None
|
| 94 |
};
|
| 95 |
let (h_c, h_u): (Tensor, Option<Tensor>) = if nivel == 0 {
|
| 96 |
+
// prefixo entra junto na 1ª avaliação
|
|
|
|
| 97 |
let prefixo_ids = Tensor::zeros((b, N_PREFIXO), candle_core::DType::U32, dev)?;
|
| 98 |
let pe = g.emb_niveis.tabela.fwd(&prefixo_ids)?;
|
| 99 |
let e = Tensor::cat(&[pe, entrada_cond], 1)?;
|
| 100 |
+
let h = self.forward_bloco(g, &e, &mut caches_cond, nivel)?;
|
| 101 |
self.nfe_contador += 1;
|
| 102 |
let hc = h.narrow(1, N_PREFIXO, n_l)?;
|
| 103 |
let hu = if let Some(eu) = entrada_uncond {
|
| 104 |
+
let eu2 = Tensor::cat(&[{
|
| 105 |
+
let prefixo_ids2 = Tensor::zeros((b, N_PREFIXO), candle_core::DType::U32, dev)?;
|
| 106 |
+
g.emb_niveis.tabela.fwd(&prefixo_ids2)?
|
| 107 |
+
}, eu], 1)?;
|
| 108 |
+
let hu = self.forward_bloco(g, &eu2, &mut caches_uncond, nivel)?;
|
| 109 |
self.nfe_contador += 1;
|
| 110 |
Some(hu.narrow(1, N_PREFIXO, n_l)?)
|
| 111 |
} else { None };
|
| 112 |
(hc, hu)
|
| 113 |
} else {
|
| 114 |
+
let h = self.forward_bloco(g, &entrada_cond, &mut caches_cond, nivel)?;
|
| 115 |
self.nfe_contador += 1;
|
| 116 |
let hu = if let Some(eu) = entrada_uncond {
|
| 117 |
+
let hu = self.forward_bloco(g, &eu, &mut caches_uncond, nivel)?;
|
| 118 |
self.nfe_contador += 1;
|
| 119 |
Some(hu)
|
| 120 |
} else { None };
|
|
|
|
| 123 |
let ids = self.amostrar_nivel(g, &h_c, h_u.as_ref(), nivel, rng, greedy)?;
|
| 124 |
codigos.push(ids.clone());
|
| 125 |
zhats = t.reconstruir_de_codigos(&codigos, nivel + 1, b, dev)?;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
}
|
| 127 |
Ok((codigos, self.nfe_contador - nfe_inicio))
|
| 128 |
}
|
|
|
|
| 132 |
&mut self,
|
| 133 |
g: &mut GeradorConceitos,
|
| 134 |
entrada: &Tensor,
|
| 135 |
+
caches: &mut Vec<CacheKv>,
|
| 136 |
nivel: usize,
|
| 137 |
) -> Result<Tensor> {
|
| 138 |
let n_novo = entrada.dim(1)?;
|
| 139 |
+
let n_cache = caches.first().and_then(|c| c.k.as_ref()).map(|k| k.dims()[2]).unwrap_or(0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
let total_vis = n_cache + n_novo;
|
| 141 |
+
// block-causal incremental: posições novas veem cache + próprio bloco
|
| 142 |
+
let m = vec![0f32; n_novo * total_vis];
|
| 143 |
+
let mascara = Tensor::from_vec(m, (n_novo, total_vis), entrada.device())?;
|
| 144 |
+
let niveis_novos = vec![nivel + 1; n_novo];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
let mut niveis_totais = vec![0usize; N_PREFIXO.min(n_cache)];
|
| 146 |
let restante = n_cache.saturating_sub(N_PREFIXO);
|
| 147 |
let mut usados = 0usize;
|
|
|
|
| 152 |
usados += tomar;
|
| 153 |
}
|
| 154 |
niveis_totais.extend(niveis_novos.clone());
|
| 155 |
+
g.tronco.forward_nivel(entrada, caches, &mascara, &niveis_novos, &niveis_totais)
|
| 156 |
}
|
| 157 |
|
| 158 |
unsafe fn amostrar_nivel(
|
|
|
|
| 164 |
rng: &mut StdRng,
|
| 165 |
greedy: bool,
|
| 166 |
) -> Result<Tensor> {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
let logits_cond = g.cabeca.cabecas[nivel].fwd(h_cond)?;
|
| 168 |
let plano = match h_uncond {
|
| 169 |
Some(hu) => {
|
|
|
|
| 176 |
};
|
| 177 |
let (b, n_l, _) = (plano.dim(0)?, plano.dim(1)?, plano.dim(2)?);
|
| 178 |
let plano2 = plano.reshape((b * n_l, g.k))?;
|
| 179 |
+
// temperatura-auto por nível (D.3)
|
| 180 |
let tau = self.calibrar_tau(&plano2, 8)?;
|
| 181 |
self.temperaturas[nivel] = tau;
|
| 182 |
+
let probs = candle_nn::ops::softmax_last_dim(&plano2.affine(1.0 / tau.max(1e-3), 0.0)?)?;
|
| 183 |
let plana = probs.flatten_all()?.to_vec1::<f32>()?;
|
| 184 |
let k = g.k;
|
| 185 |
let mut ids = Vec::with_capacity(b * n_l);
|
src/atencao.rs
CHANGED
|
@@ -3,61 +3,33 @@
|
|
| 3 |
//! de NCM"). Prova completa na memória r-04; síntese:
|
| 4 |
//!
|
| 5 |
//! (i) [paralelismo preservado] o resumo-âncora por nível h_ℓ é função
|
| 6 |
-
//! APENAS de c_{<ℓ} (computado do
|
| 7 |
//! fatoração p(c_ℓ|c_{<ℓ}) = Π_i p(c_{ℓ,i}|c_{<ℓ}) e NFE = L
|
| 8 |
//! permanecem EXATOS (Teo B.2/D.1);
|
| 9 |
-
//! (ii) [expressividade ⊇ NCM] com γ=0,
|
| 10 |
//! atenção block-causal do NCM (classe estritamente contida);
|
| 11 |
//! (iii)[planejamento reforçado] o âncora agrega o estado global dos níveis
|
| 12 |
-
//! anteriores num vetor por nível — injetado por FiLM
|
|
|
|
| 13 |
//!
|
| 14 |
-
//!
|
| 15 |
-
//! (
|
| 16 |
-
//!
|
| 17 |
-
//! logit(i,j) = ⟨q_i,k_j⟩/(√d_h·τ_{h,λ(i)}) + L_W·tanh(ξ_{h,λ(j)})
|
| 18 |
-
//! τ_{h,ℓ} = exp(T_τ·tanh(ζ_{h,ℓ}))
|
| 19 |
-
//!
|
| 20 |
-
//! **Teo A-1 (neutralidade e estabilidade).**
|
| 21 |
-
//! (i) *Nascimento neutro*: ξ=0 ⇒ peso w = e^0 = 1; ζ=0 ⇒ τ = e^0 = 1; as
|
| 22 |
-
//! operações `+0.0` e `/1.0` são exatas em f32 (exceto −0.0→+0.0, sem efeito
|
| 23 |
-
//! no softmax) ⇒ a primeira passada é bit-a-bit a atenção block-causal.
|
| 24 |
-
//! (ii) *Limites*: |log w| ≤ L_W e τ ∈ [e^{−T_τ}, e^{T_τ}] ⇒ a razão entre
|
| 25 |
-
//! pesos de nível é ≤ e^{2L_W}: o mecanismo NÃO pode colapsar o softmax em
|
| 26 |
-
//! uniforme (exigiria razão → ∞) nem saturar (τ→0). O `clamp` da v0.1.11 —
|
| 27 |
-
//! que tinha gradiente NULO fora da faixa (M6, "dead gradient") — é removido:
|
| 28 |
-
//! tanh tem gradiente sech² > 0 em todo R.
|
| 29 |
-
//! (iii) *Gradientes limitados*: |∂L/∂ζ| ≤ (T_τ/τ_min)·|Cov_p(u,ḡ)| e
|
| 30 |
-
//! |∂L/∂ξ| ≤ L_W·|Σ p·Δ| — sem modo explosivo (Teo A.1.2 da proposta C).
|
| 31 |
-
//! (iv) *Capacidade*: o espaço de reponderações vai de R^{L+1} (v0.1.11,
|
| 32 |
-
//! 1 escalar por nível para TODAS as cabeças) para R^{H·(L+1)} — com H=4,
|
| 33 |
-
//! L=7: 32 parâmetros por bloco. Estritamente mais expressivo: duas cabeças
|
| 34 |
-
//! podem confiar diferentemente no mesmo nível-fonte.
|
| 35 |
-
//! (v) *Fatoração preservada*: ξ e ζ são funções de ÍNDICES de posição
|
| 36 |
-
//! (λ(·), h) — nunca de VALORES — logo o Teo A.1.4 da proposta C aplica-se
|
| 37 |
-
//! (a distribuição preditiva p(c_ℓ|c_{<ℓ}) permanece bem definida).
|
| 38 |
|
| 39 |
use candle_core::{Result, Tensor, Var, D};
|
| 40 |
use crate::camadas::{Linear, RmsNorm, Swiglu};
|
| 41 |
|
| 42 |
-
/// Limites padrão da parametrização tanh (Teo A-1(ii)).
|
| 43 |
-
pub const LIMITE_LOG_W: f64 = 2.0; // w ∈ [e⁻², e²]
|
| 44 |
-
pub const LIMITE_TAU: f64 = 1.0; // τ ∈ [e⁻¹, e]
|
| 45 |
-
|
| 46 |
pub struct CabecaAdba {
|
| 47 |
pub q: Linear,
|
| 48 |
pub k: Linear,
|
| 49 |
pub v: Linear,
|
| 50 |
pub o: Linear,
|
| 51 |
-
///
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
/// τ_{h,ℓ} = exp(T_τ·tanh(ζ_{h,ℓ})); init 0 ⇒ τ = 1 (neutro).
|
| 56 |
-
pub zeta: Option<Var>,
|
| 57 |
pub n_cabecas: usize,
|
| 58 |
pub d_cabeca: usize,
|
| 59 |
-
pub limite_log_w: f64,
|
| 60 |
-
pub limite_tau: f64,
|
| 61 |
}
|
| 62 |
|
| 63 |
#[derive(Default, Clone)]
|
|
@@ -67,43 +39,24 @@ pub struct CacheKv {
|
|
| 67 |
}
|
| 68 |
|
| 69 |
impl CabecaAdba {
|
| 70 |
-
pub fn nova(
|
| 71 |
-
d_modelo: usize,
|
| 72 |
-
n_cabecas: usize,
|
| 73 |
-
n_niveis: usize,
|
| 74 |
-
adba_ativo: bool,
|
| 75 |
-
reponderar: bool,
|
| 76 |
-
tau_nivel: bool,
|
| 77 |
-
dev: &candle_core::Device,
|
| 78 |
-
) -> Result<Self> {
|
| 79 |
let d_cabeca = d_modelo / n_cabecas;
|
| 80 |
let desvio = 1.0 / ((d_modelo * 2) as f64).sqrt();
|
| 81 |
let mk = || Linear::sem_vies(d_modelo, d_modelo, desvio, dev);
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
Some(Var::from_tensor(&Tensor::zeros(
|
| 85 |
-
(n_cabecas, n_niveis + 1),
|
| 86 |
-
candle_core::DType::F32,
|
| 87 |
-
dev,
|
| 88 |
-
)?)?)
|
| 89 |
} else {
|
| 90 |
None
|
| 91 |
};
|
| 92 |
-
let
|
| 93 |
-
Some(Var::from_tensor(&Tensor::zeros(
|
| 94 |
-
(n_cabecas, n_niveis + 1),
|
| 95 |
-
candle_core::DType::F32,
|
| 96 |
-
dev,
|
| 97 |
-
)?)?)
|
| 98 |
} else {
|
| 99 |
None
|
| 100 |
};
|
| 101 |
Ok(CabecaAdba {
|
| 102 |
q: mk()?, k: mk()?, v: mk()?, o: mk()?,
|
| 103 |
-
|
| 104 |
n_cabecas, d_cabeca,
|
| 105 |
-
limite_log_w: LIMITE_LOG_W,
|
| 106 |
-
limite_tau: LIMITE_TAU,
|
| 107 |
})
|
| 108 |
}
|
| 109 |
|
|
@@ -124,9 +77,6 @@ impl CabecaAdba {
|
|
| 124 |
/// Atenção com máscara aditiva (T_k × T_k) e offsets de nível para a
|
| 125 |
/// reponderação/temperatura da ADBA. `niveis_query`/`niveis_chave` dizem
|
| 126 |
/// a qual nível (0=prefixo, 1..L) pertence cada posição.
|
| 127 |
-
///
|
| 128 |
-
/// Vetorizado (v0.3.0): os vieses são construídos por `index_select` —
|
| 129 |
-
/// nenhum laço `.get()` por posição (A-correção de desempenho).
|
| 130 |
pub fn attention(
|
| 131 |
&self,
|
| 132 |
x: &Tensor,
|
|
@@ -147,37 +97,30 @@ impl CabecaAdba {
|
|
| 147 |
cache.v = Some(v.clone());
|
| 148 |
let escala = 1.0 / (self.d_cabeca as f64).sqrt();
|
| 149 |
let mut scores = q.matmul(&k.transpose(2, 3)?)?.affine(escala, 0.0)?;
|
| 150 |
-
// ADBA (ii): reponderação por
|
| 151 |
-
if let (Some(
|
| 152 |
let t_k = scores.dim(3)?;
|
| 153 |
-
let
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
let
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
.tanh()?
|
| 163 |
-
.affine(self.limite_log_w as f64, 0.0)?;
|
| 164 |
-
scores = scores.broadcast_add(&bias.reshape((1, self.n_cabecas, 1, t_k))?)?;
|
| 165 |
}
|
| 166 |
-
// ADBA (iii): temperatura por
|
| 167 |
-
if let (Some(
|
| 168 |
let t_q = scores.dim(2)?;
|
| 169 |
-
let
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
let
|
| 175 |
-
.
|
| 176 |
-
|
| 177 |
-
.tanh()?
|
| 178 |
-
.affine(self.limite_tau as f64, 0.0)?
|
| 179 |
-
.exp()?;
|
| 180 |
-
scores = scores.broadcast_div(&tau.reshape((1, self.n_cabecas, t_q, 1))?)?;
|
| 181 |
}
|
| 182 |
if let Some(m) = mascara {
|
| 183 |
scores = scores.broadcast_add(m)?;
|
|
@@ -189,8 +132,8 @@ impl CabecaAdba {
|
|
| 189 |
|
| 190 |
pub fn vars(&self) -> Vec<Var> {
|
| 191 |
let mut v: Vec<Var> = [self.q.vars(), self.k.vars(), self.v.vars(), self.o.vars()].concat();
|
| 192 |
-
if let Some(w) = &self.
|
| 193 |
-
if let Some(t) = &self.
|
| 194 |
v
|
| 195 |
}
|
| 196 |
}
|
|
@@ -236,30 +179,25 @@ impl BlocoAdba {
|
|
| 236 |
/// Resumo-âncora por nível (ADBA i): h_ℓ = Attn(q_ℓ, K/V = c_{<ℓ}) — pooling
|
| 237 |
/// sobre as posições dos níveis anteriores (função de c_{<ℓ} APENAS).
|
| 238 |
pub struct AncoraNivel {
|
| 239 |
-
pub q: Var, // (
|
| 240 |
pub proj: Linear,
|
| 241 |
}
|
| 242 |
|
| 243 |
impl AncoraNivel {
|
| 244 |
pub fn nova(d: usize, n_niveis: usize, dev: &candle_core::Device) -> Result<Self> {
|
| 245 |
-
let q =
|
| 246 |
Ok(AncoraNivel { q: Var::from_tensor(&q)?, proj: Linear::novo(d, d, 0.02, dev)? })
|
| 247 |
}
|
| 248 |
|
| 249 |
-
///
|
| 250 |
-
/// → resumo (b, d). Uma única chamada de kernel por nível (a v0.1.11
|
| 251 |
-
/// fazia b chamadas sequenciais).
|
| 252 |
pub fn resumir(&self, contexto: &Tensor, nivel: usize) -> Result<Tensor> {
|
| 253 |
-
let b = contexto.dim(0)?;
|
| 254 |
let d = self.q.as_tensor().dim(1)?;
|
| 255 |
let q = self.q.as_tensor().get(nivel)?.reshape((1, 1, d))?;
|
| 256 |
-
// atenção de cabeça única
|
| 257 |
let escala = 1.0 / (d as f64).sqrt();
|
| 258 |
-
let scores = q
|
| 259 |
-
.broadcast_matmul(&contexto.transpose(1, 2)?.contiguous()?)?
|
| 260 |
-
.affine(escala, 0.0)?;
|
| 261 |
let pesos = candle_nn::ops::softmax(&scores, D::Minus1)?;
|
| 262 |
-
let resumo = pesos.matmul(contexto)?.reshape((
|
| 263 |
self.proj.fwd(&resumo)
|
| 264 |
}
|
| 265 |
|
|
@@ -269,39 +207,3 @@ impl AncoraNivel {
|
|
| 269 |
v
|
| 270 |
}
|
| 271 |
}
|
| 272 |
-
|
| 273 |
-
#[cfg(test)]
|
| 274 |
-
mod testes {
|
| 275 |
-
use super::*;
|
| 276 |
-
|
| 277 |
-
/// Gate G5 (parte): nascimento neutro — ξ=ζ=0 ⇒ w=1, τ=1 bit-a-bit.
|
| 278 |
-
#[test]
|
| 279 |
-
fn gate_nascimento_neutro_adba() -> Result<()> {
|
| 280 |
-
let dev = candle_core::Device::Cpu;
|
| 281 |
-
crate::rng::inicializar(777);
|
| 282 |
-
let x = crate::rng::randn_semeado(1.0, &[1, 12, 32], &dev)?;
|
| 283 |
-
let attn = CabecaAdba::nova(32, 4, 3, true, true, true, &dev)?;
|
| 284 |
-
let niveis = vec![0usize, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3];
|
| 285 |
-
// com ADBA (ξ=ζ=0) vs sem ADBA (None): saídas idênticas bit-a-bit
|
| 286 |
-
let mut c1 = CacheKv::default();
|
| 287 |
-
let y1 = attn.attention(&x, None, &mut c1, Some(&niveis), Some(&niveis))?;
|
| 288 |
-
let attn2 = CabecaAdba::nova(32, 4, 3, false, false, false, &dev)?;
|
| 289 |
-
// mesmos pesos q/k/v/o: copia
|
| 290 |
-
let dif = attn.q.w.as_tensor().sub(attn2.q.w.as_tensor())?.abs()?.max_all()?.to_scalar::<f32>()?;
|
| 291 |
-
assert!(dif > 0.0, "pesos distintos por semente");
|
| 292 |
-
// com pesos iguais a saída deve ser igual — testa via MESMA instância com
|
| 293 |
-
// xi/zeta zerados (já são) contra uma instância sem ADBA com mesmos pesos:
|
| 294 |
-
// em vez disso, verifica w=1 e τ=1 diretamente
|
| 295 |
-
if let Some(xi) = &attn.xi {
|
| 296 |
-
let w = xi.as_tensor().tanh()?.affine(LIMITE_LOG_W, 0.0)?.exp()?;
|
| 297 |
-
let dif = (&w - &Tensor::ones(w.shape(), w.dtype(), w.device())?)?.abs()?.max_all()?.to_scalar::<f32>()?;
|
| 298 |
-
assert_eq!(dif, 0.0, "w=1 no nascimento");
|
| 299 |
-
}
|
| 300 |
-
if let Some(zeta) = &attn.zeta {
|
| 301 |
-
let tau = zeta.as_tensor().tanh()?.affine(LIMITE_TAU, 0.0)?.exp()?;
|
| 302 |
-
let dif = (&tau - &Tensor::ones(tau.shape(), tau.dtype(), tau.device())?)?.abs()?.max_all()?.to_scalar::<f32>()?;
|
| 303 |
-
assert_eq!(dif, 0.0, "τ=1 no nascimento");
|
| 304 |
-
}
|
| 305 |
-
Ok(())
|
| 306 |
-
}
|
| 307 |
-
}
|
|
|
|
| 3 |
//! de NCM"). Prova completa na memória r-04; síntese:
|
| 4 |
//!
|
| 5 |
//! (i) [paralelismo preservado] o resumo-âncora por nível h_ℓ é função
|
| 6 |
+
//! APENAS de c_{<ℓ} (computado do cache/entrada corrente), logo a
|
| 7 |
//! fatoração p(c_ℓ|c_{<ℓ}) = Π_i p(c_{ℓ,i}|c_{<ℓ}) e NFE = L
|
| 8 |
//! permanecem EXATOS (Teo B.2/D.1);
|
| 9 |
+
//! (ii) [expressividade ⊇ NCM] com γ=0, w_ℓ'=1, τ_ℓ=1 a ADBA reduz-se à
|
| 10 |
//! atenção block-causal do NCM (classe estritamente contida);
|
| 11 |
//! (iii)[planejamento reforçado] o âncora agrega o estado global dos níveis
|
| 12 |
+
//! anteriores num vetor por nível — injetado por FiLM — fortalecendo a
|
| 13 |
+
//! hipótese H2 do Teo C.2 (condicionais consistentes dado o plano).
|
| 14 |
//!
|
| 15 |
+
//! Componentes: (a) atenção multi-cabeça com máscara aditiva arbitrária +
|
| 16 |
+
//! KV-cache; (b) reponderação aprendível w_{ℓ'} dos logits por nível-fonte;
|
| 17 |
+
//! (c) temperatura aprendível τ_ℓ por nível; (d) injeção FiLM do resumo.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
use candle_core::{Result, Tensor, Var, D};
|
| 20 |
use crate::camadas::{Linear, RmsNorm, Swiglu};
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
pub struct CabecaAdba {
|
| 23 |
pub q: Linear,
|
| 24 |
pub k: Linear,
|
| 25 |
pub v: Linear,
|
| 26 |
pub o: Linear,
|
| 27 |
+
/// Reponderação de logits por nível-fonte (len = nº de níveis + 1 p/ prefixo).
|
| 28 |
+
pub w_nivel: Option<Var>,
|
| 29 |
+
/// Temperatura aprendível por nível (log-espaço, neutro = 0).
|
| 30 |
+
pub log_tau_nivel: Option<Var>,
|
|
|
|
|
|
|
| 31 |
pub n_cabecas: usize,
|
| 32 |
pub d_cabeca: usize,
|
|
|
|
|
|
|
| 33 |
}
|
| 34 |
|
| 35 |
#[derive(Default, Clone)]
|
|
|
|
| 39 |
}
|
| 40 |
|
| 41 |
impl CabecaAdba {
|
| 42 |
+
pub fn nova(d_modelo: usize, n_cabecas: usize, n_niveis: usize, adba_ativo: bool, reponderar: bool, tau_nivel: bool, dev: &candle_core::Device) -> Result<Self> {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
let d_cabeca = d_modelo / n_cabecas;
|
| 44 |
let desvio = 1.0 / ((d_modelo * 2) as f64).sqrt();
|
| 45 |
let mk = || Linear::sem_vies(d_modelo, d_modelo, desvio, dev);
|
| 46 |
+
let w_nivel = if adba_ativo && reponderar {
|
| 47 |
+
Some(Var::from_tensor(&Tensor::ones((n_niveis + 1,), candle_core::DType::F32, dev)?)?)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
} else {
|
| 49 |
None
|
| 50 |
};
|
| 51 |
+
let log_tau_nivel = if adba_ativo && tau_nivel {
|
| 52 |
+
Some(Var::from_tensor(&Tensor::zeros((n_niveis + 1,), candle_core::DType::F32, dev)?)?)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
} else {
|
| 54 |
None
|
| 55 |
};
|
| 56 |
Ok(CabecaAdba {
|
| 57 |
q: mk()?, k: mk()?, v: mk()?, o: mk()?,
|
| 58 |
+
w_nivel, log_tau_nivel,
|
| 59 |
n_cabecas, d_cabeca,
|
|
|
|
|
|
|
| 60 |
})
|
| 61 |
}
|
| 62 |
|
|
|
|
| 77 |
/// Atenção com máscara aditiva (T_k × T_k) e offsets de nível para a
|
| 78 |
/// reponderação/temperatura da ADBA. `niveis_query`/`niveis_chave` dizem
|
| 79 |
/// a qual nível (0=prefixo, 1..L) pertence cada posição.
|
|
|
|
|
|
|
|
|
|
| 80 |
pub fn attention(
|
| 81 |
&self,
|
| 82 |
x: &Tensor,
|
|
|
|
| 97 |
cache.v = Some(v.clone());
|
| 98 |
let escala = 1.0 / (self.d_cabeca as f64).sqrt();
|
| 99 |
let mut scores = q.matmul(&k.transpose(2, 3)?)?.affine(escala, 0.0)?;
|
| 100 |
+
// ADBA (ii): reponderação por nível-fonte — score += log w_{ℓ'}
|
| 101 |
+
if let (Some(w), Some(nq), Some(nk)) = (&self.w_nivel, niveis_query, niveis_chave) {
|
| 102 |
let t_k = scores.dim(3)?;
|
| 103 |
+
let w_clamped = w.as_tensor().clamp(1e-3f32, 1e3f32)?;
|
| 104 |
+
let log_w = (&w_clamped + 1e-8)?.log()?;
|
| 105 |
+
let mut biases = vec![0f32; t_k];
|
| 106 |
+
for (j, &nivel) in nk.iter().enumerate().take(t_k) {
|
| 107 |
+
biases[j] = log_w.get(nivel)?.to_scalar::<f32>()?;
|
| 108 |
+
}
|
| 109 |
+
let _ = nq;
|
| 110 |
+
let bias = Tensor::from_vec(biases, (t_k,), x.device())?;
|
| 111 |
+
scores = scores.broadcast_add(&bias.reshape((1, 1, 1, t_k))?)?;
|
|
|
|
|
|
|
|
|
|
| 112 |
}
|
| 113 |
+
// ADBA (iii): temperatura por nível do QUERY
|
| 114 |
+
if let (Some(lt), Some(nq), _) = (&self.log_tau_nivel, niveis_query, niveis_chave) {
|
| 115 |
let t_q = scores.dim(2)?;
|
| 116 |
+
let tau = lt.as_tensor().affine(0.1, 0.0)?.exp()?; // suave (±~10% por unidade)
|
| 117 |
+
let mut fatores = vec![1f32; t_q];
|
| 118 |
+
for (i, &nivel) in nq.iter().enumerate().take(t_q) {
|
| 119 |
+
fatores[i] = tau.get(nivel)?.to_scalar::<f32>()?;
|
| 120 |
+
}
|
| 121 |
+
let f = Tensor::from_vec(fatores, (t_q,), x.device())?
|
| 122 |
+
.reshape((1, 1, t_q, 1))?;
|
| 123 |
+
scores = scores.broadcast_div(&f)?;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
}
|
| 125 |
if let Some(m) = mascara {
|
| 126 |
scores = scores.broadcast_add(m)?;
|
|
|
|
| 132 |
|
| 133 |
pub fn vars(&self) -> Vec<Var> {
|
| 134 |
let mut v: Vec<Var> = [self.q.vars(), self.k.vars(), self.v.vars(), self.o.vars()].concat();
|
| 135 |
+
if let Some(w) = &self.w_nivel { v.push(w.clone()); }
|
| 136 |
+
if let Some(t) = &self.log_tau_nivel { v.push(t.clone()); }
|
| 137 |
v
|
| 138 |
}
|
| 139 |
}
|
|
|
|
| 179 |
/// Resumo-âncora por nível (ADBA i): h_ℓ = Attn(q_ℓ, K/V = c_{<ℓ}) — pooling
|
| 180 |
/// sobre as posições dos níveis anteriores (função de c_{<ℓ} APENAS).
|
| 181 |
pub struct AncoraNivel {
|
| 182 |
+
pub q: Var, // (d,) vetor de consulta aprendível por nível — (L, d)
|
| 183 |
pub proj: Linear,
|
| 184 |
}
|
| 185 |
|
| 186 |
impl AncoraNivel {
|
| 187 |
pub fn nova(d: usize, n_niveis: usize, dev: &candle_core::Device) -> Result<Self> {
|
| 188 |
+
let q = Tensor::randn(0f32, 0.02, (n_niveis, d), dev)?;
|
| 189 |
Ok(AncoraNivel { q: Var::from_tensor(&q)?, proj: Linear::novo(d, d, 0.02, dev)? })
|
| 190 |
}
|
| 191 |
|
| 192 |
+
/// contexto (1, n_ctx, d) das posições dos níveis < ℓ → resumo (1, d).
|
|
|
|
|
|
|
| 193 |
pub fn resumir(&self, contexto: &Tensor, nivel: usize) -> Result<Tensor> {
|
|
|
|
| 194 |
let d = self.q.as_tensor().dim(1)?;
|
| 195 |
let q = self.q.as_tensor().get(nivel)?.reshape((1, 1, d))?;
|
| 196 |
+
// atenção de cabeça única: softmax(q·Kᵀ/√d)·V
|
| 197 |
let escala = 1.0 / (d as f64).sqrt();
|
| 198 |
+
let scores = q.matmul(&contexto.transpose(1, 2)?)?.affine(escala, 0.0)?;
|
|
|
|
|
|
|
| 199 |
let pesos = candle_nn::ops::softmax(&scores, D::Minus1)?;
|
| 200 |
+
let resumo = pesos.matmul(contexto)?.reshape((1, d))?;
|
| 201 |
self.proj.fwd(&resumo)
|
| 202 |
}
|
| 203 |
|
|
|
|
| 207 |
v
|
| 208 |
}
|
| 209 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/camadas.rs
CHANGED
|
@@ -5,8 +5,6 @@
|
|
| 5 |
use candle_core::{DType, Device, Result, Tensor, Var};
|
| 6 |
use candle_nn::ops::rms_norm;
|
| 7 |
|
| 8 |
-
// v0.3.0: init via RNG semeado (A19) — Tensor::randn usava entropia do SO.
|
| 9 |
-
|
| 10 |
pub struct Linear {
|
| 11 |
pub w: Var, // (d_saida, d_entrada)
|
| 12 |
pub b: Option<Var>,
|
|
@@ -14,13 +12,13 @@ pub struct Linear {
|
|
| 14 |
|
| 15 |
impl Linear {
|
| 16 |
pub fn novo(d_entrada: usize, d_saida: usize, desvio: f64, dev: &Device) -> Result<Self> {
|
| 17 |
-
let w =
|
| 18 |
let b = Tensor::zeros((d_saida,), DType::F32, dev)?;
|
| 19 |
Ok(Linear { w: Var::from_tensor(&w)?, b: Some(Var::from_tensor(&b)?) })
|
| 20 |
}
|
| 21 |
|
| 22 |
pub fn sem_vies(d_entrada: usize, d_saida: usize, desvio: f64, dev: &Device) -> Result<Self> {
|
| 23 |
-
let w =
|
| 24 |
Ok(Linear { w: Var::from_tensor(&w)?, b: None })
|
| 25 |
}
|
| 26 |
|
|
@@ -63,7 +61,7 @@ pub struct Embedding {
|
|
| 63 |
|
| 64 |
impl Embedding {
|
| 65 |
pub fn novo(vocab: usize, d: usize, dev: &Device) -> Result<Self> {
|
| 66 |
-
let t =
|
| 67 |
Ok(Embedding { tabela: Var::from_tensor(&t)? })
|
| 68 |
}
|
| 69 |
|
|
|
|
| 5 |
use candle_core::{DType, Device, Result, Tensor, Var};
|
| 6 |
use candle_nn::ops::rms_norm;
|
| 7 |
|
|
|
|
|
|
|
| 8 |
pub struct Linear {
|
| 9 |
pub w: Var, // (d_saida, d_entrada)
|
| 10 |
pub b: Option<Var>,
|
|
|
|
| 12 |
|
| 13 |
impl Linear {
|
| 14 |
pub fn novo(d_entrada: usize, d_saida: usize, desvio: f64, dev: &Device) -> Result<Self> {
|
| 15 |
+
let w = Tensor::randn(0f32, desvio as f32, (d_saida, d_entrada), dev)?;
|
| 16 |
let b = Tensor::zeros((d_saida,), DType::F32, dev)?;
|
| 17 |
Ok(Linear { w: Var::from_tensor(&w)?, b: Some(Var::from_tensor(&b)?) })
|
| 18 |
}
|
| 19 |
|
| 20 |
pub fn sem_vies(d_entrada: usize, d_saida: usize, desvio: f64, dev: &Device) -> Result<Self> {
|
| 21 |
+
let w = Tensor::randn(0f32, desvio as f32, (d_saida, d_entrada), dev)?;
|
| 22 |
Ok(Linear { w: Var::from_tensor(&w)?, b: None })
|
| 23 |
}
|
| 24 |
|
|
|
|
| 61 |
|
| 62 |
impl Embedding {
|
| 63 |
pub fn novo(vocab: usize, d: usize, dev: &Device) -> Result<Self> {
|
| 64 |
+
let t = Tensor::randn(0f32, 0.02, (vocab, d), dev)?;
|
| 65 |
Ok(Embedding { tabela: Var::from_tensor(&t)? })
|
| 66 |
}
|
| 67 |
|
src/config.rs
CHANGED
|
@@ -1,13 +1,6 @@
|
|
| 1 |
//! Config do KHTST_RUST — espelha `configs/base.json` do KHTST v23 + seção `ncm`
|
| 2 |
//! (n-04 §3) + seções novas (rust/adba/ponte/telemetria). Defaults neutros
|
| 3 |
-
//! (nascimento neutro, T10): `ncm.ativo=false` E `lambdas.conceito=0.0` (dupla
|
| 4 |
-
//! trava — v0.3.0: a trava agora é LIDA pelo código: `ncm.ativo=false` força
|
| 5 |
-
//! ADBA/SSD-Δ/mistura/λ_alg desligados).
|
| 6 |
-
//!
|
| 7 |
-
//! v0.3.0 (A25): TODAS as structs usam `#[serde(deny_unknown_fields)]` —
|
| 8 |
-
//! chave digitada errada (ex.: "d_model") agora FALHA com mensagem do serde
|
| 9 |
-
//! em vez de ser ignorada em silêncio; o `lint_config` (Levenshtein) fornece
|
| 10 |
-
//! a sugestão de correção.
|
| 11 |
|
| 12 |
use serde::{Deserialize, Serialize};
|
| 13 |
|
|
@@ -16,7 +9,6 @@ fn padrao_niveis() -> Vec<usize> {
|
|
| 16 |
}
|
| 17 |
|
| 18 |
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
| 19 |
-
#[serde(deny_unknown_fields)]
|
| 20 |
pub struct MoE {
|
| 21 |
#[serde(default = "d_true")]
|
| 22 |
pub ativo: bool,
|
|
@@ -30,7 +22,6 @@ fn d_usize2() -> usize { 2 }
|
|
| 30 |
fn d_usize4() -> usize { 4 }
|
| 31 |
|
| 32 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 33 |
-
#[serde(deny_unknown_fields)]
|
| 34 |
pub struct MTP {
|
| 35 |
#[serde(default = "d_true")]
|
| 36 |
pub ativo: bool,
|
|
@@ -49,7 +40,6 @@ impl Default for MTP {
|
|
| 49 |
fn d_f005() -> f64 { 0.05 }
|
| 50 |
|
| 51 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 52 |
-
#[serde(deny_unknown_fields)]
|
| 53 |
pub struct Modelo {
|
| 54 |
pub vocab: usize,
|
| 55 |
pub d_modelo: usize,
|
|
@@ -65,10 +55,6 @@ pub struct Modelo {
|
|
| 65 |
pub mtp: MTP,
|
| 66 |
#[serde(default = "d_true")]
|
| 67 |
pub usar_atencao_arvore: bool,
|
| 68 |
-
/// v0.3.0: porta aditiva de 2ª ordem do SSD (SSD-Δ) — percepção de
|
| 69 |
-
/// aritmética de inteiros/floats (Teo S-1). Default false = neutro.
|
| 70 |
-
#[serde(default)]
|
| 71 |
-
pub ssd_delta: bool,
|
| 72 |
}
|
| 73 |
impl Default for Modelo {
|
| 74 |
fn default() -> Self {
|
|
@@ -84,13 +70,11 @@ impl Default for Modelo {
|
|
| 84 |
moe: MoE::default(),
|
| 85 |
mtp: MTP::default(),
|
| 86 |
usar_atencao_arvore: true,
|
| 87 |
-
ssd_delta: false,
|
| 88 |
}
|
| 89 |
}
|
| 90 |
}
|
| 91 |
|
| 92 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 93 |
-
#[serde(deny_unknown_fields)]
|
| 94 |
pub struct Gsq {
|
| 95 |
pub tau0: f64,
|
| 96 |
pub tau_min: f64,
|
|
@@ -101,32 +85,23 @@ impl Default for Gsq {
|
|
| 101 |
}
|
| 102 |
|
| 103 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 104 |
-
#[serde(deny_unknown_fields)]
|
| 105 |
pub struct NestedDropout {
|
| 106 |
#[serde(default = "d_tipo_nd")]
|
| 107 |
pub tipo: String,
|
| 108 |
}
|
| 109 |
fn d_tipo_nd() -> String { "uniforme".into() }
|
| 110 |
-
impl Default for NestedDropout {
|
| 111 |
-
fn default() -> Self { NestedDropout { tipo: "uniforme".into() } }
|
| 112 |
-
}
|
| 113 |
|
| 114 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 115 |
-
#[serde(deny_unknown_fields)]
|
| 116 |
pub struct Lambdas {
|
| 117 |
pub conceito: f64,
|
| 118 |
pub rec: f64,
|
| 119 |
pub rho_ancora: f64,
|
| 120 |
-
/// v0.3.0: peso do verificador algébrico L_alg (Teo G-1). 0 = desligado.
|
| 121 |
-
#[serde(default)]
|
| 122 |
-
pub algebrico: f64,
|
| 123 |
}
|
| 124 |
impl Default for Lambdas {
|
| 125 |
-
fn default() -> Self { Lambdas { conceito: 0.0, rec: 1.0, rho_ancora: 0.05
|
| 126 |
}
|
| 127 |
|
| 128 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 129 |
-
#[serde(deny_unknown_fields)]
|
| 130 |
pub struct Temperaturas {
|
| 131 |
pub auto: bool,
|
| 132 |
pub entropia_alvo: Option<f64>,
|
|
@@ -138,7 +113,6 @@ impl Default for Temperaturas {
|
|
| 138 |
|
| 139 |
/// Seção `ncm` — schema aditivo do n-04 §3 (defaults 100% neutros).
|
| 140 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 141 |
-
#[serde(deny_unknown_fields)]
|
| 142 |
pub struct Ncm {
|
| 143 |
pub ativo: bool,
|
| 144 |
#[serde(default = "padrao_niveis")]
|
|
@@ -147,12 +121,10 @@ pub struct Ncm {
|
|
| 147 |
pub k_por_nivel: usize,
|
| 148 |
pub ema_codebook: f64,
|
| 149 |
pub beta_commit: f64,
|
| 150 |
-
#[serde(default)]
|
| 151 |
pub nested_dropout: NestedDropout,
|
| 152 |
pub corrupcao_cond: f64,
|
| 153 |
pub dropout_cfg: f64,
|
| 154 |
pub cfg_scale: f64,
|
| 155 |
-
#[serde(default)]
|
| 156 |
pub gsq: Gsq,
|
| 157 |
#[serde(default = "d_pesos_nivel")]
|
| 158 |
pub pesos_nivel: String,
|
|
@@ -160,9 +132,8 @@ pub struct Ncm {
|
|
| 160 |
pub lambdas: Lambdas,
|
| 161 |
#[serde(default)]
|
| 162 |
pub temperaturas: Temperaturas,
|
| 163 |
-
#[serde(default)]
|
| 164 |
pub mbr: Mbr,
|
| 165 |
-
/// Modo da escala R_ℓ dos codebooks: "ema_projecao" | "otima_teo".
|
| 166 |
#[serde(default = "d_escala_r")]
|
| 167 |
pub escala_r: String,
|
| 168 |
/// Modo de quantização: "gsq_rco" (padrão, Teo D.4) | "ste_ema" (Teo A.0).
|
|
@@ -171,16 +142,10 @@ pub struct Ncm {
|
|
| 171 |
/// Níveis como tokens (modo D.6 — MTP equivalente).
|
| 172 |
#[serde(default)]
|
| 173 |
pub niveis_token: bool,
|
| 174 |
-
/// v0.3.0 — MISTURA DE PLANOS (raciocínio em subníveis paralelos):
|
| 175 |
-
/// nº de planos M por nível na cabeça (Teo C-1). 1 = cabeça produto
|
| 176 |
-
/// da v0.1.11 (bit-a-bit). ≥ 2 = mistura.
|
| 177 |
-
#[serde(default = "d_um")]
|
| 178 |
-
pub mistura_planos: usize,
|
| 179 |
}
|
| 180 |
fn d_pesos_nivel() -> String { "uniforme".into() }
|
| 181 |
fn d_escala_r() -> String { "ema_projecao".into() }
|
| 182 |
fn d_modo_quant() -> String { "gsq_rco".into() }
|
| 183 |
-
fn d_um() -> usize { 1 }
|
| 184 |
|
| 185 |
impl Default for Ncm {
|
| 186 |
fn default() -> Self {
|
|
@@ -203,13 +168,11 @@ impl Default for Ncm {
|
|
| 203 |
escala_r: "ema_projecao".into(),
|
| 204 |
modo_quantizacao: "gsq_rco".into(),
|
| 205 |
niveis_token: false,
|
| 206 |
-
mistura_planos: 1,
|
| 207 |
}
|
| 208 |
}
|
| 209 |
}
|
| 210 |
|
| 211 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 212 |
-
#[serde(deny_unknown_fields)]
|
| 213 |
pub struct Mbr {
|
| 214 |
pub n_cand: usize,
|
| 215 |
}
|
|
@@ -219,33 +182,23 @@ impl Default for Mbr {
|
|
| 219 |
|
| 220 |
/// ADBA — Atenção Dual-Bloco Adaptativa (aprimoramento 1; prova em r-04).
|
| 221 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 222 |
-
#[serde(deny_unknown_fields)]
|
| 223 |
pub struct Adba {
|
| 224 |
pub ativo: bool,
|
| 225 |
/// Peso FiLM do resumo-âncora por nível (0 = NCM puro).
|
| 226 |
pub gamma_ancora: f64,
|
| 227 |
-
/// Reponderação aprendível dos logits por
|
| 228 |
pub reponderacao_nivel: bool,
|
| 229 |
-
/// Temperatura aprendível por
|
| 230 |
pub temperatura_nivel: bool,
|
| 231 |
-
/// v0.3.0: limite |log w| da reponderação (Teo A-1(ii); padrão 2.0).
|
| 232 |
-
#[serde(default = "d_lim_w")]
|
| 233 |
-
pub limite_log_w: f64,
|
| 234 |
-
/// v0.3.0: limite τ ∈ [e⁻x, eˣ] (padrão 1.0).
|
| 235 |
-
#[serde(default = "d_lim_tau")]
|
| 236 |
-
pub limite_tau: f64,
|
| 237 |
}
|
| 238 |
-
fn d_lim_w() -> f64 { 2.0 }
|
| 239 |
-
fn d_lim_tau() -> f64 { 1.0 }
|
| 240 |
impl Default for Adba {
|
| 241 |
fn default() -> Self {
|
| 242 |
-
Adba { ativo: true, gamma_ancora: 1.0, reponderacao_nivel: true, temperatura_nivel: true
|
| 243 |
}
|
| 244 |
}
|
| 245 |
|
| 246 |
/// Ponte Dupla — comunicação paralela bidirecional entre modelos (aprimoramento 2; prova em r-05).
|
| 247 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 248 |
-
#[serde(deny_unknown_fields)]
|
| 249 |
pub struct Ponte {
|
| 250 |
pub ativo: bool,
|
| 251 |
pub capacidade: usize,
|
|
@@ -257,7 +210,6 @@ impl Default for Ponte {
|
|
| 257 |
}
|
| 258 |
|
| 259 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 260 |
-
#[serde(deny_unknown_fields)]
|
| 261 |
pub struct Treino {
|
| 262 |
pub lr: f64,
|
| 263 |
pub aquecimento: usize,
|
|
@@ -268,16 +220,8 @@ pub struct Treino {
|
|
| 268 |
pub peso_decaimento: f64,
|
| 269 |
#[serde(default = "d_usize25")]
|
| 270 |
pub telemetria_a_cada: usize,
|
| 271 |
-
/// v0.3.0 (A19): semente do RNG global (reprodutibilidade bit-a-bit).
|
| 272 |
-
#[serde(default = "d_semente")]
|
| 273 |
-
pub semente: u64,
|
| 274 |
-
/// v0.3.0: checkpoint periódico a cada N passos (0 = só no fim) —
|
| 275 |
-
/// proteção contra o encerramento em background do ambiente.
|
| 276 |
-
#[serde(default)]
|
| 277 |
-
pub checkpoint_a_cada: usize,
|
| 278 |
}
|
| 279 |
fn d_usize25() -> usize { 25 }
|
| 280 |
-
fn d_semente() -> u64 { 317 }
|
| 281 |
impl Default for Treino {
|
| 282 |
fn default() -> Self {
|
| 283 |
Treino {
|
|
@@ -289,23 +233,17 @@ impl Default for Treino {
|
|
| 289 |
clip: 1.0,
|
| 290 |
peso_decaimento: 0.01,
|
| 291 |
telemetria_a_cada: 25,
|
| 292 |
-
semente: 317,
|
| 293 |
-
checkpoint_a_cada: 0,
|
| 294 |
}
|
| 295 |
}
|
| 296 |
}
|
| 297 |
|
| 298 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 299 |
-
#[serde(deny_unknown_fields)]
|
| 300 |
pub struct TelemetriaCfg {
|
| 301 |
pub jsonl: bool,
|
| 302 |
pub deteccao_aprendizado: bool,
|
| 303 |
pub ajuste_automatico: bool,
|
| 304 |
pub paciencia_plateau: usize,
|
| 305 |
pub fator_lr_plateau: f64,
|
| 306 |
-
/// A24: truncar o JSONL no início (default false = append).
|
| 307 |
-
#[serde(default)]
|
| 308 |
-
pub truncar_jsonl: bool,
|
| 309 |
}
|
| 310 |
impl Default for TelemetriaCfg {
|
| 311 |
fn default() -> Self {
|
|
@@ -315,26 +253,19 @@ impl Default for TelemetriaCfg {
|
|
| 315 |
ajuste_automatico: true,
|
| 316 |
paciencia_plateau: 200,
|
| 317 |
fator_lr_plateau: 0.6,
|
| 318 |
-
truncar_jsonl: false,
|
| 319 |
}
|
| 320 |
}
|
| 321 |
}
|
| 322 |
|
| 323 |
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
| 324 |
-
#[serde(deny_unknown_fields)]
|
| 325 |
pub struct Dados {
|
| 326 |
-
#[serde(default)]
|
| 327 |
pub shard_treino: String,
|
| 328 |
-
#[serde(default)]
|
| 329 |
pub shard_validacao: String,
|
| 330 |
-
#[serde(default)]
|
| 331 |
pub max_artigos: usize,
|
| 332 |
-
#[serde(default)]
|
| 333 |
pub max_bytes: usize,
|
| 334 |
}
|
| 335 |
|
| 336 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
| 337 |
-
#[serde(deny_unknown_fields)]
|
| 338 |
pub struct MemoriaCfg {
|
| 339 |
pub som_lado: usize,
|
| 340 |
pub lambda_jepa: f64,
|
|
@@ -345,7 +276,6 @@ impl Default for MemoriaCfg {
|
|
| 345 |
}
|
| 346 |
|
| 347 |
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
| 348 |
-
#[serde(deny_unknown_fields)]
|
| 349 |
pub struct ConfigKhtst {
|
| 350 |
#[serde(default)]
|
| 351 |
pub modelo: Modelo,
|
|
@@ -396,48 +326,22 @@ impl ConfigKhtst {
|
|
| 396 |
if self.treino.lote == 0 || self.treino.lr <= 0.0 {
|
| 397 |
return Err("treino.lote >= 1 e treino.lr > 0 exigidos".into());
|
| 398 |
}
|
| 399 |
-
if n.mistura_planos == 0 {
|
| 400 |
-
return Err("ncm.mistura_planos >= 1 exigido (1 = cabeça produto neutra)".into());
|
| 401 |
-
}
|
| 402 |
Ok(())
|
| 403 |
}
|
| 404 |
|
| 405 |
/// L (número de níveis) — Teo B.2(ii).
|
| 406 |
pub fn l_niveis(&self) -> usize { self.ncm.niveis.len() }
|
| 407 |
|
| 408 |
-
/// Verificação de orçamento do KV-cache (T2 +
|
| 409 |
-
/// `ctx + soma − ctx` = soma (508), IGNORANDO as 8 posições de prefixo —
|
| 410 |
-
/// a sequência real tem 516. Agora: `N_PREFIXO + Σ n_ℓ` (gate G6).
|
| 411 |
pub fn checar_orcamento_cache(&self) -> Result<usize, String> {
|
| 412 |
let soma: usize = self.ncm.niveis.iter().copied().sum();
|
| 413 |
-
let
|
| 414 |
-
let usado = n_prefixo + soma;
|
| 415 |
if usado > self.modelo.kv_cache_max {
|
| 416 |
return Err(format!(
|
| 417 |
-
"orçamento do KV-cache violado (T2): {}
|
| 418 |
-
usado,
|
| 419 |
));
|
| 420 |
}
|
| 421 |
Ok(usado)
|
| 422 |
}
|
| 423 |
}
|
| 424 |
-
|
| 425 |
-
#[cfg(test)]
|
| 426 |
-
mod testes {
|
| 427 |
-
use super::*;
|
| 428 |
-
|
| 429 |
-
/// Gate G6: orçamento com prefixo = 516 (a v0.1.11 devolvia 508).
|
| 430 |
-
#[test]
|
| 431 |
-
fn orcamento_cache_inclui_prefixo() {
|
| 432 |
-
let cfg = ConfigKhtst::default();
|
| 433 |
-
assert_eq!(cfg.checar_orcamento_cache().unwrap(), 8 + 508);
|
| 434 |
-
}
|
| 435 |
-
|
| 436 |
-
/// A25: chave digitada errada deve FALHAR (a v0.1.11 ignorava em silêncio).
|
| 437 |
-
#[test]
|
| 438 |
-
fn chave_errada_falha() {
|
| 439 |
-
let txt = r#"{"modelo": {"vocab": 100, "d_model": 192}}"#;
|
| 440 |
-
let r: Result<ConfigKhtst, _> = serde_json::from_str(txt);
|
| 441 |
-
assert!(r.is_err(), "d_model (typo) deve ser rejeitado por deny_unknown_fields");
|
| 442 |
-
}
|
| 443 |
-
}
|
|
|
|
| 1 |
//! Config do KHTST_RUST — espelha `configs/base.json` do KHTST v23 + seção `ncm`
|
| 2 |
//! (n-04 §3) + seções novas (rust/adba/ponte/telemetria). Defaults neutros
|
| 3 |
+
//! (nascimento neutro, T10): `ncm.ativo=false` E `lambdas.conceito=0.0` (dupla trava).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
|
| 5 |
use serde::{Deserialize, Serialize};
|
| 6 |
|
|
|
|
| 9 |
}
|
| 10 |
|
| 11 |
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
|
|
| 12 |
pub struct MoE {
|
| 13 |
#[serde(default = "d_true")]
|
| 14 |
pub ativo: bool,
|
|
|
|
| 22 |
fn d_usize4() -> usize { 4 }
|
| 23 |
|
| 24 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 25 |
pub struct MTP {
|
| 26 |
#[serde(default = "d_true")]
|
| 27 |
pub ativo: bool,
|
|
|
|
| 40 |
fn d_f005() -> f64 { 0.05 }
|
| 41 |
|
| 42 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 43 |
pub struct Modelo {
|
| 44 |
pub vocab: usize,
|
| 45 |
pub d_modelo: usize,
|
|
|
|
| 55 |
pub mtp: MTP,
|
| 56 |
#[serde(default = "d_true")]
|
| 57 |
pub usar_atencao_arvore: bool,
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
}
|
| 59 |
impl Default for Modelo {
|
| 60 |
fn default() -> Self {
|
|
|
|
| 70 |
moe: MoE::default(),
|
| 71 |
mtp: MTP::default(),
|
| 72 |
usar_atencao_arvore: true,
|
|
|
|
| 73 |
}
|
| 74 |
}
|
| 75 |
}
|
| 76 |
|
| 77 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 78 |
pub struct Gsq {
|
| 79 |
pub tau0: f64,
|
| 80 |
pub tau_min: f64,
|
|
|
|
| 85 |
}
|
| 86 |
|
| 87 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 88 |
pub struct NestedDropout {
|
| 89 |
#[serde(default = "d_tipo_nd")]
|
| 90 |
pub tipo: String,
|
| 91 |
}
|
| 92 |
fn d_tipo_nd() -> String { "uniforme".into() }
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 95 |
pub struct Lambdas {
|
| 96 |
pub conceito: f64,
|
| 97 |
pub rec: f64,
|
| 98 |
pub rho_ancora: f64,
|
|
|
|
|
|
|
|
|
|
| 99 |
}
|
| 100 |
impl Default for Lambdas {
|
| 101 |
+
fn default() -> Self { Lambdas { conceito: 0.0, rec: 1.0, rho_ancora: 0.05 } }
|
| 102 |
}
|
| 103 |
|
| 104 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 105 |
pub struct Temperaturas {
|
| 106 |
pub auto: bool,
|
| 107 |
pub entropia_alvo: Option<f64>,
|
|
|
|
| 113 |
|
| 114 |
/// Seção `ncm` — schema aditivo do n-04 §3 (defaults 100% neutros).
|
| 115 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 116 |
pub struct Ncm {
|
| 117 |
pub ativo: bool,
|
| 118 |
#[serde(default = "padrao_niveis")]
|
|
|
|
| 121 |
pub k_por_nivel: usize,
|
| 122 |
pub ema_codebook: f64,
|
| 123 |
pub beta_commit: f64,
|
|
|
|
| 124 |
pub nested_dropout: NestedDropout,
|
| 125 |
pub corrupcao_cond: f64,
|
| 126 |
pub dropout_cfg: f64,
|
| 127 |
pub cfg_scale: f64,
|
|
|
|
| 128 |
pub gsq: Gsq,
|
| 129 |
#[serde(default = "d_pesos_nivel")]
|
| 130 |
pub pesos_nivel: String,
|
|
|
|
| 132 |
pub lambdas: Lambdas,
|
| 133 |
#[serde(default)]
|
| 134 |
pub temperaturas: Temperaturas,
|
|
|
|
| 135 |
pub mbr: Mbr,
|
| 136 |
+
/// Modo da escala R_ℓ dos codebooks: "ema_projecao" | "otima_teo" | "aprendida" (r-01 §4.4).
|
| 137 |
#[serde(default = "d_escala_r")]
|
| 138 |
pub escala_r: String,
|
| 139 |
/// Modo de quantização: "gsq_rco" (padrão, Teo D.4) | "ste_ema" (Teo A.0).
|
|
|
|
| 142 |
/// Níveis como tokens (modo D.6 — MTP equivalente).
|
| 143 |
#[serde(default)]
|
| 144 |
pub niveis_token: bool,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
}
|
| 146 |
fn d_pesos_nivel() -> String { "uniforme".into() }
|
| 147 |
fn d_escala_r() -> String { "ema_projecao".into() }
|
| 148 |
fn d_modo_quant() -> String { "gsq_rco".into() }
|
|
|
|
| 149 |
|
| 150 |
impl Default for Ncm {
|
| 151 |
fn default() -> Self {
|
|
|
|
| 168 |
escala_r: "ema_projecao".into(),
|
| 169 |
modo_quantizacao: "gsq_rco".into(),
|
| 170 |
niveis_token: false,
|
|
|
|
| 171 |
}
|
| 172 |
}
|
| 173 |
}
|
| 174 |
|
| 175 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 176 |
pub struct Mbr {
|
| 177 |
pub n_cand: usize,
|
| 178 |
}
|
|
|
|
| 182 |
|
| 183 |
/// ADBA — Atenção Dual-Bloco Adaptativa (aprimoramento 1; prova em r-04).
|
| 184 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 185 |
pub struct Adba {
|
| 186 |
pub ativo: bool,
|
| 187 |
/// Peso FiLM do resumo-âncora por nível (0 = NCM puro).
|
| 188 |
pub gamma_ancora: f64,
|
| 189 |
+
/// Reponderação aprendível dos logits por nível-fonte (true = ativa).
|
| 190 |
pub reponderacao_nivel: bool,
|
| 191 |
+
/// Temperatura aprendível por nível (true = ativa).
|
| 192 |
pub temperatura_nivel: bool,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
}
|
|
|
|
|
|
|
| 194 |
impl Default for Adba {
|
| 195 |
fn default() -> Self {
|
| 196 |
+
Adba { ativo: true, gamma_ancora: 1.0, reponderacao_nivel: true, temperatura_nivel: true }
|
| 197 |
}
|
| 198 |
}
|
| 199 |
|
| 200 |
/// Ponte Dupla — comunicação paralela bidirecional entre modelos (aprimoramento 2; prova em r-05).
|
| 201 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 202 |
pub struct Ponte {
|
| 203 |
pub ativo: bool,
|
| 204 |
pub capacidade: usize,
|
|
|
|
| 210 |
}
|
| 211 |
|
| 212 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 213 |
pub struct Treino {
|
| 214 |
pub lr: f64,
|
| 215 |
pub aquecimento: usize,
|
|
|
|
| 220 |
pub peso_decaimento: f64,
|
| 221 |
#[serde(default = "d_usize25")]
|
| 222 |
pub telemetria_a_cada: usize,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
}
|
| 224 |
fn d_usize25() -> usize { 25 }
|
|
|
|
| 225 |
impl Default for Treino {
|
| 226 |
fn default() -> Self {
|
| 227 |
Treino {
|
|
|
|
| 233 |
clip: 1.0,
|
| 234 |
peso_decaimento: 0.01,
|
| 235 |
telemetria_a_cada: 25,
|
|
|
|
|
|
|
| 236 |
}
|
| 237 |
}
|
| 238 |
}
|
| 239 |
|
| 240 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 241 |
pub struct TelemetriaCfg {
|
| 242 |
pub jsonl: bool,
|
| 243 |
pub deteccao_aprendizado: bool,
|
| 244 |
pub ajuste_automatico: bool,
|
| 245 |
pub paciencia_plateau: usize,
|
| 246 |
pub fator_lr_plateau: f64,
|
|
|
|
|
|
|
|
|
|
| 247 |
}
|
| 248 |
impl Default for TelemetriaCfg {
|
| 249 |
fn default() -> Self {
|
|
|
|
| 253 |
ajuste_automatico: true,
|
| 254 |
paciencia_plateau: 200,
|
| 255 |
fator_lr_plateau: 0.6,
|
|
|
|
| 256 |
}
|
| 257 |
}
|
| 258 |
}
|
| 259 |
|
| 260 |
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
|
|
|
| 261 |
pub struct Dados {
|
|
|
|
| 262 |
pub shard_treino: String,
|
|
|
|
| 263 |
pub shard_validacao: String,
|
|
|
|
| 264 |
pub max_artigos: usize,
|
|
|
|
| 265 |
pub max_bytes: usize,
|
| 266 |
}
|
| 267 |
|
| 268 |
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
|
|
| 269 |
pub struct MemoriaCfg {
|
| 270 |
pub som_lado: usize,
|
| 271 |
pub lambda_jepa: f64,
|
|
|
|
| 276 |
}
|
| 277 |
|
| 278 |
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
|
|
|
| 279 |
pub struct ConfigKhtst {
|
| 280 |
#[serde(default)]
|
| 281 |
pub modelo: Modelo,
|
|
|
|
| 326 |
if self.treino.lote == 0 || self.treino.lr <= 0.0 {
|
| 327 |
return Err("treino.lote >= 1 e treino.lr > 0 exigidos".into());
|
| 328 |
}
|
|
|
|
|
|
|
|
|
|
| 329 |
Ok(())
|
| 330 |
}
|
| 331 |
|
| 332 |
/// L (número de níveis) — Teo B.2(ii).
|
| 333 |
pub fn l_niveis(&self) -> usize { self.ncm.niveis.len() }
|
| 334 |
|
| 335 |
+
/// Verificação de orçamento do KV-cache (T2): |cond| + Σ_{j<L} n_j ≤ kv_cache_max.
|
|
|
|
|
|
|
| 336 |
pub fn checar_orcamento_cache(&self) -> Result<usize, String> {
|
| 337 |
let soma: usize = self.ncm.niveis.iter().copied().sum();
|
| 338 |
+
let usado = self.modelo.comprimento_ctx + soma - self.modelo.comprimento_ctx; // cond + níveis < L
|
|
|
|
| 339 |
if usado > self.modelo.kv_cache_max {
|
| 340 |
return Err(format!(
|
| 341 |
+
"orçamento do KV-cache violado (T2): {} > kv_cache_max = {}",
|
| 342 |
+
usado, self.modelo.kv_cache_max
|
| 343 |
));
|
| 344 |
}
|
| 345 |
Ok(usado)
|
| 346 |
}
|
| 347 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/controlador.rs
CHANGED
|
@@ -2,38 +2,19 @@
|
|
| 2 |
//! automatizados de todos hiper parâmetros"). TODA decisão é registrada na
|
| 3 |
//! telemetria com motivo (auditável).
|
| 4 |
//!
|
| 5 |
-
//!
|
| 6 |
-
//!
|
| 7 |
-
//!
|
| 8 |
-
//!
|
| 9 |
-
//!
|
| 10 |
-
//!
|
| 11 |
-
//!
|
| 12 |
-
//! O LR aplicado é lr_t = m_t · lr_prog(t). Então:
|
| 13 |
-
//! (i) m_t ≤ 1 sempre (o multiplicador nunca AMPLIA o LR além da agenda);
|
| 14 |
-
//! (ii) após a última divergência em t*, m_t → 1 exponencialmente:
|
| 15 |
-
//! m_t = 1 − (1 − 0,3)·(1+κ)^{−(t−t*)} ⇒ lim lr_t = lr_prog(t);
|
| 16 |
-
//! (iii) entre divergências consecutivas a distância ≥
|
| 17 |
-
//! log(0,7)/log(1+κ) passos é necessária para recuperação total.
|
| 18 |
-
//! *Prova:* (i) por indução (cap em 1); (ii) resolução da recorrência
|
| 19 |
-
//! linear m_{t+1} = m_t(1+κ) capada em 1; (iii) 0,3·(1+κ)^n ≥ 1. ∎
|
| 20 |
-
//!
|
| 21 |
-
//! ## v0.3.0 — integrações (código morto ligado, S-08/S-09 da auditoria B)
|
| 22 |
-
//! - `calibrar_epsilon` agora é CHAMADO pela fase B com a taxa de erro por
|
| 23 |
-
//! nível medida (C.3(ii) — cobertura);
|
| 24 |
-
//! - `lambda_ancora.passo` (hipergradiente exato, Teo 29.2) é chamado pela
|
| 25 |
-
//! fase B com L_diff medido;
|
| 26 |
-
//! - passo com perda não finita é DESCARTADO ANTES de aplicar (M10 — a
|
| 27 |
-
//! v0.1.11 aplicava pesos NaN e gravava checkpoint com NaN).
|
| 28 |
|
| 29 |
use crate::telemetria::TelemetryHub;
|
| 30 |
|
| 31 |
pub struct Controlador {
|
| 32 |
pub lr_base: f64,
|
| 33 |
pub lr_corrente: f64,
|
| 34 |
-
/// M9: multiplicador com recuperação (substitui o ratchet)
|
| 35 |
-
pub multiplicador: f64,
|
| 36 |
-
pub taxa_recuperacao: f64,
|
| 37 |
pub aquecimento: usize,
|
| 38 |
pub passos_totais: usize,
|
| 39 |
pub paciencia_plateau: usize,
|
|
@@ -52,8 +33,6 @@ impl Controlador {
|
|
| 52 |
Controlador {
|
| 53 |
lr_base,
|
| 54 |
lr_corrente: lr_base,
|
| 55 |
-
multiplicador: 1.0,
|
| 56 |
-
taxa_recuperacao: 0.02, // recupera ~2%/passo (≥42 passos p/ voltar de 0,3 a 1)
|
| 57 |
aquecimento,
|
| 58 |
passos_totais,
|
| 59 |
paciencia_plateau: paciencia,
|
|
@@ -68,59 +47,44 @@ impl Controlador {
|
|
| 68 |
}
|
| 69 |
}
|
| 70 |
|
| 71 |
-
/// LR do passo
|
| 72 |
pub fn lr_passo(&mut self, passo: usize, perda: f64, hub: &mut TelemetryHub) -> f64 {
|
| 73 |
-
// divergência?
|
| 74 |
-
// aqui apenas reduz o multiplicador.
|
| 75 |
if !perda.is_finite() || perda > 2.0 * self.minimo_ema {
|
| 76 |
-
let de = self.
|
| 77 |
-
self.
|
| 78 |
-
hub.ajuste(
|
| 79 |
-
"
|
| 80 |
-
de,
|
| 81 |
-
self.multiplicador,
|
| 82 |
-
&format!("divergência (perda {perda:.3e} vs mínimo EMA {:.3e}) — passo descartado (M10)", self.minimo_ema),
|
| 83 |
-
);
|
| 84 |
self.passos_sem_melhora = 0;
|
| 85 |
-
self.
|
| 86 |
-
return self.lr_prog(passo) * self.multiplicador;
|
| 87 |
}
|
| 88 |
-
// rastreio de mínimo
|
| 89 |
if perda < self.minimo_ema * 0.999 {
|
| 90 |
self.minimo_ema = self.minimo_ema.min(perda);
|
| 91 |
self.passos_sem_melhora = 0;
|
| 92 |
} else {
|
| 93 |
self.passos_sem_melhora += 1;
|
| 94 |
}
|
| 95 |
-
// plateau
|
| 96 |
if self.paciencia_plateau > 0 && self.passos_sem_melhora >= self.paciencia_plateau {
|
| 97 |
-
let de = self.
|
| 98 |
-
self.
|
| 99 |
-
hub.ajuste(
|
| 100 |
-
"
|
| 101 |
-
de,
|
| 102 |
-
self.multiplicador,
|
| 103 |
-
&format!("plateau: {} passos sem melhora (multiplicador — reversível)", self.passos_sem_melhora),
|
| 104 |
-
);
|
| 105 |
self.passos_sem_melhora = 0;
|
| 106 |
}
|
| 107 |
-
//
|
| 108 |
-
if self.multiplicador < 1.0 {
|
| 109 |
-
self.multiplicador = (self.multiplicador * (1.0 + self.taxa_recuperacao)).min(1.0);
|
| 110 |
-
}
|
| 111 |
-
self.lr_corrente = self.lr_prog(passo) * self.multiplicador;
|
| 112 |
-
self.lr_corrente
|
| 113 |
-
}
|
| 114 |
-
|
| 115 |
-
fn lr_prog(&self, passo: usize) -> f64 {
|
| 116 |
let p = passo as f64;
|
| 117 |
-
if p < self.aquecimento as f64 {
|
| 118 |
self.lr_base * (p + 1.0) / self.aquecimento.max(1) as f64
|
| 119 |
} else {
|
| 120 |
let t = (p - self.aquecimento as f64) / (self.passos_totais_clamped() - self.aquecimento).max(1) as f64;
|
| 121 |
let cosseno = 0.5 * (1.0 + (std::f64::consts::PI * t.min(1.0)).cos());
|
| 122 |
self.lr_base * (0.1 + 0.9 * cosseno)
|
| 123 |
-
}
|
|
|
|
|
|
|
|
|
|
| 124 |
}
|
| 125 |
|
| 126 |
fn passos_totais_clamped(&self) -> usize {
|
|
@@ -151,7 +115,6 @@ impl Controlador {
|
|
| 151 |
|
| 152 |
/// ε da corrupção (C.3(ii)): sobe se a taxa de erro por nível em validação
|
| 153 |
/// excede ε (cobertura insuficiente), desce lentamente caso contrário.
|
| 154 |
-
/// v0.3.0: INTEGRADA — chamada pela fase B com a taxa medida.
|
| 155 |
pub fn calibrar_epsilon(&mut self, taxa_erro_por_nivel: f64, hub: &mut TelemetryHub) -> f64 {
|
| 156 |
let de = self.epsilon_corrupcao;
|
| 157 |
if taxa_erro_por_nivel > self.epsilon_corrupcao + 0.02 {
|
|
@@ -165,45 +128,4 @@ impl Controlador {
|
|
| 165 |
}
|
| 166 |
self.epsilon_corrupcao
|
| 167 |
}
|
| 168 |
-
|
| 169 |
-
/// λ-âncora (Teo 29.2, hipergradiente EXATO): L(λ) = λ·L_diff + ρ(λ−λ₀)²
|
| 170 |
-
/// ⇒ ∂L/∂λ = L_diff + 2ρ(λ−λ₀); ótimo interior λ* = λ₀ − L_diff/(2ρ).
|
| 171 |
-
/// v0.3.0: INTEGRADA — chamada pela fase B a cada passo com L_diff medido.
|
| 172 |
-
pub fn passo_lambda(&mut self, l_diff: f64, hub: &mut TelemetryHub) -> f64 {
|
| 173 |
-
let de = self.lambda_ancora.lambda;
|
| 174 |
-
let novo = self.lambda_ancora.passo(l_diff, 0.05);
|
| 175 |
-
if (de - novo).abs() > 1e-9 {
|
| 176 |
-
hub.ajuste("lambda_jepa", de, novo,
|
| 177 |
-
&format!("hipergradiente exato 29.2: L_diff={l_diff:.4e}, λ*={:.4}", self.lambda_ancora.otimo_analitico(l_diff)));
|
| 178 |
-
}
|
| 179 |
-
novo
|
| 180 |
-
}
|
| 181 |
-
}
|
| 182 |
-
|
| 183 |
-
#[cfg(test)]
|
| 184 |
-
mod testes {
|
| 185 |
-
use super::*;
|
| 186 |
-
|
| 187 |
-
/// Teo C-2: multiplicador nunca excede 1 e RECUPERA após divergência
|
| 188 |
-
/// (a v0.1.11 tinha ratchet monotônico — M9).
|
| 189 |
-
#[test]
|
| 190 |
-
fn multiplicador_recupera() {
|
| 191 |
-
let mut hub = TelemetryHub::novo(None);
|
| 192 |
-
let mut c = Controlador::novo(1e-3, 2, 1000, 5, 0.6, 0.05);
|
| 193 |
-
// divergência no passo 10
|
| 194 |
-
c.minimo_ema = 1.0;
|
| 195 |
-
c.lr_passo(10, 10.0, &mut hub);
|
| 196 |
-
assert!((c.multiplicador - 0.3).abs() < 1e-9, "divergência: mult 0,3");
|
| 197 |
-
// 100 passos com perda DECRESCENTE (sem plateau): multiplicador
|
| 198 |
-
// volta a 1 (recuperação — Teo C-2(ii))
|
| 199 |
-
for p in 11..111 {
|
| 200 |
-
let perda = 0.5 * 0.98f64.powi(p as i32);
|
| 201 |
-
c.lr_passo(p, perda, &mut hub);
|
| 202 |
-
}
|
| 203 |
-
assert!(c.multiplicador > 0.999, "recuperação completa (Teo C-2(ii)): {}", c.multiplicador);
|
| 204 |
-
assert!(c.multiplicador <= 1.0 + 1e-12, "nunca amplifica (Teo C-2(i))");
|
| 205 |
-
// LR final ≈ agenda (não fica preso no piso)
|
| 206 |
-
let esperado = c.lr_prog(110);
|
| 207 |
-
assert!((c.lr_corrente - esperado).abs() / esperado < 1e-9);
|
| 208 |
-
}
|
| 209 |
}
|
|
|
|
| 2 |
//! automatizados de todos hiper parâmetros"). TODA decisão é registrada na
|
| 3 |
//! telemetria com motivo (auditável).
|
| 4 |
//!
|
| 5 |
+
//! - LR: aquecimento → cosseno; plateau (paciência) → ×fator; divergência
|
| 6 |
+
//! (NaN ou perda > 2× mínimo EMA) → ×0,3 + rollback do estado;
|
| 7 |
+
//! - λ (âncora, Teo 29.2): hipergradiente exato;
|
| 8 |
+
//! - ε da corrupção (Teo C.3(ii)): calibra pela taxa de erro por nível em
|
| 9 |
+
//! validação (cobertura — "calibrar ρ tal que erro ≤ ρ");
|
| 10 |
+
//! - clip: adapta ao percentil da norma de gradiente;
|
| 11 |
+
//! - τ do GSQ: agenda analítica (Teo 30.6) — no tokenizador.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
use crate::telemetria::TelemetryHub;
|
| 14 |
|
| 15 |
pub struct Controlador {
|
| 16 |
pub lr_base: f64,
|
| 17 |
pub lr_corrente: f64,
|
|
|
|
|
|
|
|
|
|
| 18 |
pub aquecimento: usize,
|
| 19 |
pub passos_totais: usize,
|
| 20 |
pub paciencia_plateau: usize,
|
|
|
|
| 33 |
Controlador {
|
| 34 |
lr_base,
|
| 35 |
lr_corrente: lr_base,
|
|
|
|
|
|
|
| 36 |
aquecimento,
|
| 37 |
passos_totais,
|
| 38 |
paciencia_plateau: paciencia,
|
|
|
|
| 47 |
}
|
| 48 |
}
|
| 49 |
|
| 50 |
+
/// LR do passo (aquecimento → cosseno com decaimentos por plateau).
|
| 51 |
pub fn lr_passo(&mut self, passo: usize, perda: f64, hub: &mut TelemetryHub) -> f64 {
|
| 52 |
+
// divergência?
|
|
|
|
| 53 |
if !perda.is_finite() || perda > 2.0 * self.minimo_ema {
|
| 54 |
+
let de = self.lr_corrente;
|
| 55 |
+
self.lr_corrente *= 0.3;
|
| 56 |
+
hub.ajuste("lr", de, self.lr_corrente,
|
| 57 |
+
&format!("divergência detectada (perda {perda:.3e} vs mínimo EMA {:.3e})", self.minimo_ema));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
self.passos_sem_melhora = 0;
|
| 59 |
+
return self.lr_corrente;
|
|
|
|
| 60 |
}
|
| 61 |
+
// rastreio de mínimo (EMA implícito pela suavização do próprio valor)
|
| 62 |
if perda < self.minimo_ema * 0.999 {
|
| 63 |
self.minimo_ema = self.minimo_ema.min(perda);
|
| 64 |
self.passos_sem_melhora = 0;
|
| 65 |
} else {
|
| 66 |
self.passos_sem_melhora += 1;
|
| 67 |
}
|
| 68 |
+
// plateau
|
| 69 |
if self.paciencia_plateau > 0 && self.passos_sem_melhora >= self.paciencia_plateau {
|
| 70 |
+
let de = self.lr_corrente;
|
| 71 |
+
self.lr_corrente *= self.fator_plateau;
|
| 72 |
+
hub.ajuste("lr", de, self.lr_corrente,
|
| 73 |
+
&format!("plateau: {} passos sem melhora", self.passos_sem_melhora));
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
self.passos_sem_melhora = 0;
|
| 75 |
}
|
| 76 |
+
// programação base: aquecimento + cosseno
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
let p = passo as f64;
|
| 78 |
+
let lr_prog = if p < self.aquecimento as f64 {
|
| 79 |
self.lr_base * (p + 1.0) / self.aquecimento.max(1) as f64
|
| 80 |
} else {
|
| 81 |
let t = (p - self.aquecimento as f64) / (self.passos_totais_clamped() - self.aquecimento).max(1) as f64;
|
| 82 |
let cosseno = 0.5 * (1.0 + (std::f64::consts::PI * t.min(1.0)).cos());
|
| 83 |
self.lr_base * (0.1 + 0.9 * cosseno)
|
| 84 |
+
};
|
| 85 |
+
// o menor entre a programação e o teto pós-plateau
|
| 86 |
+
self.lr_corrente = self.lr_corrente.min(lr_prog).max(self.lr_base * 0.01);
|
| 87 |
+
self.lr_corrente
|
| 88 |
}
|
| 89 |
|
| 90 |
fn passos_totais_clamped(&self) -> usize {
|
|
|
|
| 115 |
|
| 116 |
/// ε da corrupção (C.3(ii)): sobe se a taxa de erro por nível em validação
|
| 117 |
/// excede ε (cobertura insuficiente), desce lentamente caso contrário.
|
|
|
|
| 118 |
pub fn calibrar_epsilon(&mut self, taxa_erro_por_nivel: f64, hub: &mut TelemetryHub) -> f64 {
|
| 119 |
let de = self.epsilon_corrupcao;
|
| 120 |
if taxa_erro_por_nivel > self.epsilon_corrupcao + 0.02 {
|
|
|
|
| 128 |
}
|
| 129 |
self.epsilon_corrupcao
|
| 130 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
}
|
src/diagnostico.rs
CHANGED
|
@@ -9,35 +9,35 @@
|
|
| 9 |
|
| 10 |
use std::collections::BTreeMap;
|
| 11 |
|
| 12 |
-
/// Chaves canônicas conhecidas do config
|
| 13 |
-
/// PRÓPRIO `ConfigKhtst` via serde (fonte única, runtime) em vez de uma lista
|
| 14 |
-
/// escrita à mão (a v0.1.11 listava 7 chaves inexistentes: roteador,
|
| 15 |
-
/// microunidades, atencao_moe, moe_enc_dec, autoajuste, mamba3, sonicmoe —
|
| 16 |
-
/// sugestões de lint INVÁLIDAS).
|
| 17 |
pub fn chaves_conhecidas() -> Vec<String> {
|
| 18 |
let mut v = vec![];
|
| 19 |
for secao in ["modelo", "ncm", "adba", "ponte", "treino", "telemetria", "dados", "memoria"] {
|
| 20 |
v.push(secao.to_string());
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
}
|
| 38 |
}
|
| 39 |
-
v.sort();
|
| 40 |
-
v.dedup();
|
| 41 |
v
|
| 42 |
}
|
| 43 |
|
|
|
|
| 9 |
|
| 10 |
use std::collections::BTreeMap;
|
| 11 |
|
| 12 |
+
/// Chaves canônicas conhecidas do config (espeelho do config.rs).
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
pub fn chaves_conhecidas() -> Vec<String> {
|
| 14 |
let mut v = vec![];
|
| 15 |
for secao in ["modelo", "ncm", "adba", "ponte", "treino", "telemetria", "dados", "memoria"] {
|
| 16 |
v.push(secao.to_string());
|
| 17 |
+
let campos: &[&str] = match secao {
|
| 18 |
+
"modelo" => &["vocab", "d_modelo", "n_camadas", "n_cabecas", "d_ff",
|
| 19 |
+
"comprimento_ctx", "janela_sliding", "kv_cache_max", "usar_atencao_arvore",
|
| 20 |
+
"moe", "mtp", "roteador", "microunidades", "atencao_moe",
|
| 21 |
+
"moe_enc_dec", "autoajuste", "mamba3", "sonicmoe"],
|
| 22 |
+
"ncm" => &["ativo", "niveis", "d_lat", "k_por_nivel", "ema_codebook", "beta_commit",
|
| 23 |
+
"corrupcao_cond", "dropout_cfg", "cfg_scale", "pesos_nivel", "escala_r",
|
| 24 |
+
"modo_quantizacao", "niveis_token",
|
| 25 |
+
// subseções (dicts aninhados — 2º nível)
|
| 26 |
+
"nested_dropout", "gsq", "lambdas", "temperaturas", "mbr"],
|
| 27 |
+
"adba" => &["ativo", "gamma_ancora", "reponderacao_nivel", "temperatura_nivel"],
|
| 28 |
+
"ponte" => &["ativo", "capacidade", "timeout_ms", "rodadas_demo"],
|
| 29 |
+
"treino" => &["lr", "aquecimento", "passos_fase_a", "passos_fase_b", "lote", "clip",
|
| 30 |
+
"peso_decaimento", "telemetria_a_cada"],
|
| 31 |
+
"telemetria" => &["jsonl", "deteccao_aprendizado", "ajuste_automatico",
|
| 32 |
+
"paciencia_plateau", "fator_lr_plateau"],
|
| 33 |
+
"dados" => &["shard_treino", "shard_validacao", "max_artigos", "max_bytes"],
|
| 34 |
+
"memoria" => &["som_lado", "lambda_jepa", "rm_dois_tempos"],
|
| 35 |
+
_ => &[],
|
| 36 |
+
};
|
| 37 |
+
for c in campos {
|
| 38 |
+
v.push(format!("{secao}.{c}"));
|
| 39 |
}
|
| 40 |
}
|
|
|
|
|
|
|
| 41 |
v
|
| 42 |
}
|
| 43 |
|
src/gerador.rs
CHANGED
|
@@ -4,56 +4,22 @@
|
|
| 4 |
//! condicionamento = reconstrução cumulativa interpolada ẑ_{ℓ−1}^{(n_ℓ)} + e_ℓ
|
| 5 |
//! (função determinística de c_{<ℓ} — Teo D.1). Corrupção κ (Teo C.3) e
|
| 6 |
//! dropout CFG (Teo D.2(iii)). Cabeças MTP auxiliares (Teo D.6/T7).
|
| 7 |
-
//!
|
| 8 |
-
//! ## v0.3.0 — APRIMORAMENTOS
|
| 9 |
-
//! 1. **Cabeça de mistura de planos** (`CabecaMistura` — tronco.rs):
|
| 10 |
-
//! p(c_ℓ|c_{<ℓ}) = Σ_m w_m Π_i q_{m,i} — raciocínio em M subníveis
|
| 11 |
-
//! PARALELOS por nível, combinados por mistura. Provas: Teo C-1 (tronco.rs).
|
| 12 |
-
//! 2. **Verificador algébrico da hierarquia** (E.3 da proposta C):
|
| 13 |
-
//! L_alg = Σ_{ℓ≥2} ‖Interp_{n_{ℓ−1}}(ẑ_ℓ^{soft}) − ẑ_{ℓ−1}‖² —
|
| 14 |
-
//! consistência de refino: a reconstrução do nível ℓ, projetada de volta
|
| 15 |
-
//! à granularidade ℓ−1, deve PRESERVAR o conteúdo grosseiro.
|
| 16 |
-
//!
|
| 17 |
-
//! **Teo G-1 (verificador algébrico).**
|
| 18 |
-
//! (i) L_alg é QUADRÁTICA nas esperanças de código: com q_i = distribuição
|
| 19 |
-
//! por posição e r_soft = Σ_k q_i(k)·R·c_k, L_alg(r) = ‖D·r − v‖² com
|
| 20 |
-
//! D = Interp_{n_{ℓ−1}←n_ℓ} (linear) e v = (I − D·U)ẑ_{ℓ−1} + (D·U − I)ẑ_{ℓ−1}
|
| 21 |
-
//! … i.e. v = ẑ_{ℓ−1} − D·U·ẑ_{ℓ−1} (o "desfoque" a compensar).
|
| 22 |
-
//! (ii) O minimizador relaxado (r livre) satisfaz as equações normais
|
| 23 |
-
//! DᵀDr* = Dᵀv — solução em forma fechada (mínimos quadrados), e o valor
|
| 24 |
-
//! mínimo ‖v‖² − ‖D r*‖²… é COTA INFERIOR para qualquer distribuição de
|
| 25 |
-
//! códigos (o span dos códigos é subespaço).
|
| 26 |
-
//! (iii) Com codebook Stiefel (linhas ortonormais, C Cᵀ = I_K), a projeção
|
| 27 |
-
//! de um alvo t ∈ R^d no span {R·c_k} é R·C·t/R·… computável em O(K·d):
|
| 28 |
-
//! P_span(t) = R·Cᵀ⟨…⟩ — barato e auditável.
|
| 29 |
-
//! (iv) Gradiente exato via autograd (todas as ops diferenciáveis) —
|
| 30 |
-
//! validado por diferenças finitas no gate G11.
|
| 31 |
-
//! *Prova (i):* Interp é matriz fixa W ∈ R^{n_{ℓ−1}×n_ℓ}; L_alg(r) =
|
| 32 |
-
//! ‖W·r − v‖₂² é quadrática com Hessiano 2WᵀW ⪰ 0 (convexa); (ii) segue
|
| 33 |
-
//! da otimalidade de Gauss-Markov; (iii) da ortonormalidade das linhas de
|
| 34 |
-
//! C (Teo 26.3). ∎
|
| 35 |
-
//! **Critério de falsificação**: a série `recomposicao_residuo` deve CAIR
|
| 36 |
-
//! ao longo do treino da fase A (a hierarquia fica algébricamente
|
| 37 |
-
//! consistente); se subir, o verificador acusa inconsistência.
|
| 38 |
|
| 39 |
use candle_core::{Device, Result, Tensor, Var, D};
|
| 40 |
use crate::camadas::{Linear, RmsNorm};
|
| 41 |
use crate::config::ConfigKhtst;
|
| 42 |
use crate::mascaras::{interp_para, mascara_block_causal};
|
| 43 |
use crate::tokenizador::TokenizadorConceitos;
|
| 44 |
-
use crate::tronco::{CabecaConceitos,
|
| 45 |
use rand::prelude::*;
|
| 46 |
|
| 47 |
-
pub
|
| 48 |
|
| 49 |
pub struct GeradorConceitos {
|
| 50 |
pub tronco: Tronco,
|
| 51 |
pub emb_niveis: EmbNiveis,
|
| 52 |
pub proj_entrada: Linear, // d_lat → d_modelo
|
| 53 |
-
/// Cabeça produto da v0.1.11 — usada quando M = 1 (bit-a-bit idêntica).
|
| 54 |
pub cabeca: CabecaConceitos,
|
| 55 |
-
/// Cabeças de MISTURA DE PLANOS por nível (v0.3.0) — usadas quando M ≥ 2.
|
| 56 |
-
pub cabecas_mistura: Vec<CabecaMistura>,
|
| 57 |
pub cabecas_mtp: Vec<Linear>, // auxiliares — Teo D.6
|
| 58 |
pub norm_entrada: RmsNorm,
|
| 59 |
pub mascara: Tensor,
|
|
@@ -64,8 +30,6 @@ pub struct GeradorConceitos {
|
|
| 64 |
pub k: usize,
|
| 65 |
pub mtp_ativo: bool,
|
| 66 |
pub beta_mtp: f64,
|
| 67 |
-
pub m_planos: usize,
|
| 68 |
-
pub lambda_alg: f64,
|
| 69 |
}
|
| 70 |
|
| 71 |
pub struct SaidaTreino {
|
|
@@ -74,19 +38,12 @@ pub struct SaidaTreino {
|
|
| 74 |
pub ce_mtp: f64,
|
| 75 |
pub acuracia_media: f64,
|
| 76 |
pub uncond: bool,
|
| 77 |
-
/// verificador algébrico (medido nos códigos verdadeiros — telemetria)
|
| 78 |
-
pub residuo_algebrico: f64,
|
| 79 |
-
/// perda algébrica diferenciável (soft — para PCGrad separada)
|
| 80 |
-
pub perda_alg: Option<Tensor>,
|
| 81 |
-
/// entropia média dos pesos de plano (telemetria da mistura)
|
| 82 |
-
pub entropia_planos: f64,
|
| 83 |
}
|
| 84 |
|
| 85 |
impl GeradorConceitos {
|
| 86 |
pub fn novo(cfg: &ConfigKhtst, dev: &Device) -> Result<Self> {
|
| 87 |
let m = &cfg.modelo;
|
| 88 |
let n = &cfg.ncm;
|
| 89 |
-
crate::rng::inicializar(cfg.treino.semente ^ 0xB);
|
| 90 |
let tronco = Tronco::novo(cfg, dev)?;
|
| 91 |
let mascara = mascara_block_causal(&n.niveis, N_PREFIXO, dev)?;
|
| 92 |
let mut mtp = Vec::new();
|
|
@@ -95,19 +52,11 @@ impl GeradorConceitos {
|
|
| 95 |
mtp.push(Linear::sem_vies(m.d_modelo, n.k_por_nivel, 0.02, dev)?);
|
| 96 |
}
|
| 97 |
}
|
| 98 |
-
let m_planos = if cfg.ncm.ativo { n.mistura_planos.max(1) } else { 1 };
|
| 99 |
-
let mut cabecas_mistura = Vec::new();
|
| 100 |
-
if m_planos >= 2 {
|
| 101 |
-
for _ in 0..n.niveis.len() {
|
| 102 |
-
cabecas_mistura.push(CabecaMistura::nova(m.d_modelo, n.k_por_nivel, m_planos, dev)?);
|
| 103 |
-
}
|
| 104 |
-
}
|
| 105 |
Ok(GeradorConceitos {
|
| 106 |
tronco,
|
| 107 |
emb_niveis: EmbNiveis::nova(cfg, dev)?,
|
| 108 |
proj_entrada: Linear::sem_vies(n.d_lat, m.d_modelo, 0.02, dev)?,
|
| 109 |
cabeca: CabecaConceitos::nova(cfg, dev)?,
|
| 110 |
-
cabecas_mistura,
|
| 111 |
cabecas_mtp: mtp,
|
| 112 |
norm_entrada: RmsNorm::novo(m.d_modelo, 1e-5, dev)?,
|
| 113 |
mascara,
|
|
@@ -118,8 +67,6 @@ impl GeradorConceitos {
|
|
| 118 |
k: n.k_por_nivel,
|
| 119 |
mtp_ativo: m.mtp.ativo,
|
| 120 |
beta_mtp: m.mtp.beta_entropia,
|
| 121 |
-
m_planos,
|
| 122 |
-
lambda_alg: if cfg.ncm.ativo { cfg.ncm.lambdas.algebrico } else { 0.0 },
|
| 123 |
})
|
| 124 |
}
|
| 125 |
|
|
@@ -141,6 +88,7 @@ impl GeradorConceitos {
|
|
| 141 |
dev: &Device,
|
| 142 |
) -> Result<Tensor> {
|
| 143 |
let n_l = self.niveis[nivel];
|
|
|
|
| 144 |
let d = self.proj_entrada.w.as_tensor().dims()[0];
|
| 145 |
let base = match zhat_anterior {
|
| 146 |
Some(z) => {
|
|
@@ -174,29 +122,9 @@ impl GeradorConceitos {
|
|
| 174 |
Ok(saida)
|
| 175 |
}
|
| 176 |
|
| 177 |
-
/// Verificador algébrico (Teo G-1): resíduo de recomposição da
|
| 178 |
-
/// hierarquia nos códigos VERDADEIROS (telemetria/falsificação).
|
| 179 |
-
pub fn residuo_algebrico(zhats: &[Tensor], dev: &Device) -> Result<f64> {
|
| 180 |
-
if zhats.len() < 2 {
|
| 181 |
-
return Ok(0.0);
|
| 182 |
-
}
|
| 183 |
-
let mut soma = 0f64;
|
| 184 |
-
let mut cont = 0usize;
|
| 185 |
-
for l in 1..zhats.len() {
|
| 186 |
-
let n_ant = zhats[l - 1].dim(1)?;
|
| 187 |
-
let voltou = interp_para(&zhats[l], n_ant, dev)?;
|
| 188 |
-
let dif = (&voltou - &zhats[l - 1])?;
|
| 189 |
-
let m = dif.sqr()?.mean_all()?.to_scalar::<f32>()? as f64;
|
| 190 |
-
soma += m;
|
| 191 |
-
cont += 1;
|
| 192 |
-
}
|
| 193 |
-
Ok(if cont > 0 { soma / cont as f64 } else { 0.0 })
|
| 194 |
-
}
|
| 195 |
-
|
| 196 |
/// Forward de TREINO com perda (1 passada — Teo D.1(iv)): teacher forcing
|
| 197 |
/// com códigos VERDADEIROS no alvo; condicionamento corrompido por κ;
|
| 198 |
-
/// dropout CFG por exemplo
|
| 199 |
-
/// (M = 1, bit-a-bit v0.1.11). Inclui o verificador algébrico soft.
|
| 200 |
pub fn perda_treino(
|
| 201 |
&self,
|
| 202 |
tok: &TokenizadorConceitos,
|
|
@@ -240,76 +168,35 @@ impl GeradorConceitos {
|
|
| 240 |
let mut ces = Vec::with_capacity(self.niveis.len());
|
| 241 |
let mut accs = Vec::new();
|
| 242 |
let mut inicio = N_PREFIXO;
|
| 243 |
-
let usar_mistura = self.m_planos >= 2 && !self.cabecas_mistura.is_empty();
|
| 244 |
-
let mut entropia_planos = 0f64;
|
| 245 |
-
let mut n_entropia = 0usize;
|
| 246 |
-
// verificador algébrico SOFT (diferenciável) — acumulado por nível
|
| 247 |
-
let mut perda_alg: Option<Tensor> = None;
|
| 248 |
-
let mut ces_tensores: Vec<Tensor> = Vec::with_capacity(self.niveis.len());
|
| 249 |
for (i, &n_l) in self.niveis.iter().enumerate() {
|
| 250 |
let trecho = h.narrow(1, inicio, n_l)?;
|
|
|
|
|
|
|
| 251 |
let alvo = codigos_verdadeiros[i].reshape(b * n_l)?.to_dtype(candle_core::DType::U32)?;
|
| 252 |
-
let
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
/
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
let preds = plano.argmax(D::Minus1)?;
|
| 259 |
-
let ac = preds.eq(&alvo)?.to_dtype(candle_core::DType::F32)?.sum_all()?.to_scalar::<f32>()? as f64
|
| 260 |
-
/ (b * n_l) as f64;
|
| 261 |
-
// entropia dos pesos de plano (telemetria)
|
| 262 |
-
let w = cab.pesos_planos(&trecho)?;
|
| 263 |
-
let wl = ((&w + 1e-10f64)? * &(&w + 1e-10f64)?.log()?)?;
|
| 264 |
-
let hent = wl.affine(-1.0, 0.0)?.sum(D::Minus1)?.mean_all()?.to_scalar::<f32>()? as f64;
|
| 265 |
-
entropia_planos += hent;
|
| 266 |
-
n_entropia += 1;
|
| 267 |
-
// verificador algébrico soft (Teo G-1): r_soft do nível i
|
| 268 |
-
if self.lambda_alg > 0.0 && i >= 1 {
|
| 269 |
-
// softmax COM backward (softmax_last_dim é no_bwd no candle 0.11)
|
| 270 |
-
let probs = candle_nn::ops::softmax(&plano, D::Minus1)?;
|
| 271 |
-
let probs3 = probs.reshape((b, n_l, self.k))?;
|
| 272 |
-
let r_soft = tok.materializar_probs_nivel(i, &probs3)?;
|
| 273 |
-
let zhat_soft = (&interp_para(&zhats[i - 1], n_l, dev)? + &r_soft)?;
|
| 274 |
-
let n_ant = self.niveis[i - 1];
|
| 275 |
-
let voltou = interp_para(&zhat_soft, n_ant, dev)?;
|
| 276 |
-
let dif = (&voltou - &zhats[i - 1])?;
|
| 277 |
-
let termo = dif.sqr()?.mean_all()?;
|
| 278 |
-
perda_alg = Some(match perda_alg {
|
| 279 |
-
Some(p) => (&p + &termo)?,
|
| 280 |
-
None => termo,
|
| 281 |
-
});
|
| 282 |
-
}
|
| 283 |
-
(nll, ce, ac)
|
| 284 |
-
} else {
|
| 285 |
-
let logits = self.cabeca.cabecas[i].fwd(&trecho)?;
|
| 286 |
-
let plano = logits.reshape((b * n_l, self.k))?;
|
| 287 |
-
let ce = candle_nn::loss::cross_entropy(&plano, &alvo)?;
|
| 288 |
-
let preds = plano.argmax(D::Minus1)?;
|
| 289 |
-
let ac = preds.eq(&alvo)?.to_dtype(candle_core::DType::F32)?.sum_all()?.to_scalar::<f32>()? as f64
|
| 290 |
-
/ (b * n_l) as f64;
|
| 291 |
-
(ce.clone(), ce.to_scalar::<f32>()? as f64, ac)
|
| 292 |
-
};
|
| 293 |
-
ces.push(ce_v);
|
| 294 |
-
accs.push(acc);
|
| 295 |
-
ces_tensores.push(ce_t);
|
| 296 |
inicio += n_l;
|
| 297 |
}
|
| 298 |
-
// somatório das CEs (média por nível)
|
| 299 |
let l = ces.len() as f64;
|
| 300 |
-
let mut perda =
|
| 301 |
-
for
|
| 302 |
-
perda = (&perda + &
|
| 303 |
}
|
| 304 |
// MTP auxiliar (Teo D.6): cabeça no nível ℓ prevê códigos do nível ℓ+1
|
| 305 |
let mut ce_mtp = 0f64;
|
| 306 |
if self.mtp_ativo && !self.cabecas_mtp.is_empty() {
|
|
|
|
| 307 |
let mut inicio = N_PREFIXO;
|
| 308 |
let mut soma = None;
|
| 309 |
for i in 0..self.cabecas_mtp.len() {
|
| 310 |
let n_l = self.niveis[i];
|
| 311 |
let n_prox = self.niveis[i + 1];
|
| 312 |
let trecho = h.narrow(1, inicio, n_l)?;
|
|
|
|
| 313 |
let trecho_mtp = if n_l != n_prox {
|
| 314 |
interp_para(&trecho, n_prox, dev)?
|
| 315 |
} else {
|
|
@@ -329,17 +216,12 @@ impl GeradorConceitos {
|
|
| 329 |
perda = (&perda + &s.affine(self.beta_mtp / m, 0.0)?)?;
|
| 330 |
}
|
| 331 |
}
|
| 332 |
-
// resíduo algébrico nos códigos verdadeiros (telemetria)
|
| 333 |
-
let residuo = Self::residuo_algebrico(&zhats, dev)?;
|
| 334 |
Ok(SaidaTreino {
|
| 335 |
perda,
|
| 336 |
-
ce_por_nivel: ces,
|
| 337 |
ce_mtp,
|
| 338 |
acuracia_media: accs.iter().sum::<f64>() / accs.len() as f64,
|
| 339 |
uncond,
|
| 340 |
-
residuo_algebrico: residuo,
|
| 341 |
-
perda_alg,
|
| 342 |
-
entropia_planos: if n_entropia > 0 { entropia_planos / n_entropia as f64 } else { 0.0 },
|
| 343 |
})
|
| 344 |
}
|
| 345 |
|
|
@@ -348,32 +230,25 @@ impl GeradorConceitos {
|
|
| 348 |
v.extend(self.emb_niveis.vars());
|
| 349 |
v.extend(self.proj_entrada.vars());
|
| 350 |
v.extend(self.cabeca.vars());
|
| 351 |
-
for cm in &self.cabecas_mistura {
|
| 352 |
-
v.extend(cm.vars());
|
| 353 |
-
}
|
| 354 |
for m in &self.cabecas_mtp { v.extend(m.vars()); }
|
| 355 |
v.extend(self.norm_entrada.vars());
|
| 356 |
v
|
| 357 |
}
|
| 358 |
|
| 359 |
pub fn salvar(&self, caminho: &str) -> Result<()> {
|
| 360 |
-
let vars = self.vars();
|
| 361 |
let mut obj = serde_json::Map::new();
|
| 362 |
-
|
| 363 |
-
"formato": "khtst-ckpt-v2",
|
| 364 |
-
"tipo": "gerador",
|
| 365 |
-
"n_vars": vars.len(),
|
| 366 |
-
"formas": vars.iter().map(|v| v.as_tensor().dims()).collect::<Vec<_>>(),
|
| 367 |
-
}));
|
| 368 |
-
for (i, v) in vars.iter().enumerate() {
|
| 369 |
let dados = v.as_tensor().flatten_all()?.to_vec1::<f32>()?;
|
| 370 |
obj.insert(format!("v{i}"), serde_json::json!({
|
| 371 |
-
"forma": v.as_tensor().dims(),
|
| 372 |
"dados": dados,
|
| 373 |
}));
|
| 374 |
}
|
| 375 |
let txt = serde_json::to_string(&serde_json::Value::Object(obj))
|
| 376 |
.map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?;
|
| 377 |
-
|
|
|
|
|
|
|
|
|
|
| 378 |
}
|
| 379 |
}
|
|
|
|
| 4 |
//! condicionamento = reconstrução cumulativa interpolada ẑ_{ℓ−1}^{(n_ℓ)} + e_ℓ
|
| 5 |
//! (função determinística de c_{<ℓ} — Teo D.1). Corrupção κ (Teo C.3) e
|
| 6 |
//! dropout CFG (Teo D.2(iii)). Cabeças MTP auxiliares (Teo D.6/T7).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
use candle_core::{Device, Result, Tensor, Var, D};
|
| 9 |
use crate::camadas::{Linear, RmsNorm};
|
| 10 |
use crate::config::ConfigKhtst;
|
| 11 |
use crate::mascaras::{interp_para, mascara_block_causal};
|
| 12 |
use crate::tokenizador::TokenizadorConceitos;
|
| 13 |
+
use crate::tronco::{CabecaConceitos, EmbNiveis, Tronco};
|
| 14 |
use rand::prelude::*;
|
| 15 |
|
| 16 |
+
pub const N_PREFIXO: usize = 8;
|
| 17 |
|
| 18 |
pub struct GeradorConceitos {
|
| 19 |
pub tronco: Tronco,
|
| 20 |
pub emb_niveis: EmbNiveis,
|
| 21 |
pub proj_entrada: Linear, // d_lat → d_modelo
|
|
|
|
| 22 |
pub cabeca: CabecaConceitos,
|
|
|
|
|
|
|
| 23 |
pub cabecas_mtp: Vec<Linear>, // auxiliares — Teo D.6
|
| 24 |
pub norm_entrada: RmsNorm,
|
| 25 |
pub mascara: Tensor,
|
|
|
|
| 30 |
pub k: usize,
|
| 31 |
pub mtp_ativo: bool,
|
| 32 |
pub beta_mtp: f64,
|
|
|
|
|
|
|
| 33 |
}
|
| 34 |
|
| 35 |
pub struct SaidaTreino {
|
|
|
|
| 38 |
pub ce_mtp: f64,
|
| 39 |
pub acuracia_media: f64,
|
| 40 |
pub uncond: bool,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
}
|
| 42 |
|
| 43 |
impl GeradorConceitos {
|
| 44 |
pub fn novo(cfg: &ConfigKhtst, dev: &Device) -> Result<Self> {
|
| 45 |
let m = &cfg.modelo;
|
| 46 |
let n = &cfg.ncm;
|
|
|
|
| 47 |
let tronco = Tronco::novo(cfg, dev)?;
|
| 48 |
let mascara = mascara_block_causal(&n.niveis, N_PREFIXO, dev)?;
|
| 49 |
let mut mtp = Vec::new();
|
|
|
|
| 52 |
mtp.push(Linear::sem_vies(m.d_modelo, n.k_por_nivel, 0.02, dev)?);
|
| 53 |
}
|
| 54 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
Ok(GeradorConceitos {
|
| 56 |
tronco,
|
| 57 |
emb_niveis: EmbNiveis::nova(cfg, dev)?,
|
| 58 |
proj_entrada: Linear::sem_vies(n.d_lat, m.d_modelo, 0.02, dev)?,
|
| 59 |
cabeca: CabecaConceitos::nova(cfg, dev)?,
|
|
|
|
| 60 |
cabecas_mtp: mtp,
|
| 61 |
norm_entrada: RmsNorm::novo(m.d_modelo, 1e-5, dev)?,
|
| 62 |
mascara,
|
|
|
|
| 67 |
k: n.k_por_nivel,
|
| 68 |
mtp_ativo: m.mtp.ativo,
|
| 69 |
beta_mtp: m.mtp.beta_entropia,
|
|
|
|
|
|
|
| 70 |
})
|
| 71 |
}
|
| 72 |
|
|
|
|
| 88 |
dev: &Device,
|
| 89 |
) -> Result<Tensor> {
|
| 90 |
let n_l = self.niveis[nivel];
|
| 91 |
+
// dims()[0] = d_modelo (SAÍDA da projeção d_lat→d_modelo)
|
| 92 |
let d = self.proj_entrada.w.as_tensor().dims()[0];
|
| 93 |
let base = match zhat_anterior {
|
| 94 |
Some(z) => {
|
|
|
|
| 122 |
Ok(saida)
|
| 123 |
}
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
/// Forward de TREINO com perda (1 passada — Teo D.1(iv)): teacher forcing
|
| 126 |
/// com códigos VERDADEIROS no alvo; condicionamento corrompido por κ;
|
| 127 |
+
/// dropout CFG por exemplo (ramo não-condicionado = códigos aleatórios).
|
|
|
|
| 128 |
pub fn perda_treino(
|
| 129 |
&self,
|
| 130 |
tok: &TokenizadorConceitos,
|
|
|
|
| 168 |
let mut ces = Vec::with_capacity(self.niveis.len());
|
| 169 |
let mut accs = Vec::new();
|
| 170 |
let mut inicio = N_PREFIXO;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
for (i, &n_l) in self.niveis.iter().enumerate() {
|
| 172 |
let trecho = h.narrow(1, inicio, n_l)?;
|
| 173 |
+
let logits = self.cabeca.cabecas[i].fwd(&trecho)?;
|
| 174 |
+
let plano = logits.reshape((b * n_l, self.k))?;
|
| 175 |
let alvo = codigos_verdadeiros[i].reshape(b * n_l)?.to_dtype(candle_core::DType::U32)?;
|
| 176 |
+
let ce = candle_nn::loss::cross_entropy(&plano, &alvo)?;
|
| 177 |
+
let preds = plano.argmax(D::Minus1)?;
|
| 178 |
+
let ac = preds.eq(&alvo)?.to_dtype(candle_core::DType::F32)?.sum_all()?.to_scalar::<f32>()? as f64
|
| 179 |
+
/ (b * n_l) as f64;
|
| 180 |
+
accs.push(ac);
|
| 181 |
+
ces.push(ce);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
inicio += n_l;
|
| 183 |
}
|
|
|
|
| 184 |
let l = ces.len() as f64;
|
| 185 |
+
let mut perda = ces[0].affine(1.0 / l, 0.0)?;
|
| 186 |
+
for ce in &ces[1..] {
|
| 187 |
+
perda = (&perda + &ce.affine(1.0 / l, 0.0)?)?;
|
| 188 |
}
|
| 189 |
// MTP auxiliar (Teo D.6): cabeça no nível ℓ prevê códigos do nível ℓ+1
|
| 190 |
let mut ce_mtp = 0f64;
|
| 191 |
if self.mtp_ativo && !self.cabecas_mtp.is_empty() {
|
| 192 |
+
let dev = self.mascara.device();
|
| 193 |
let mut inicio = N_PREFIXO;
|
| 194 |
let mut soma = None;
|
| 195 |
for i in 0..self.cabecas_mtp.len() {
|
| 196 |
let n_l = self.niveis[i];
|
| 197 |
let n_prox = self.niveis[i + 1];
|
| 198 |
let trecho = h.narrow(1, inicio, n_l)?;
|
| 199 |
+
// reamostra para o tamanho do nível alvo (n_ℓ ≠ n_{ℓ+1})
|
| 200 |
let trecho_mtp = if n_l != n_prox {
|
| 201 |
interp_para(&trecho, n_prox, dev)?
|
| 202 |
} else {
|
|
|
|
| 216 |
perda = (&perda + &s.affine(self.beta_mtp / m, 0.0)?)?;
|
| 217 |
}
|
| 218 |
}
|
|
|
|
|
|
|
| 219 |
Ok(SaidaTreino {
|
| 220 |
perda,
|
| 221 |
+
ce_por_nivel: ces.iter().map(|t| t.to_scalar::<f32>().unwrap() as f64).collect(),
|
| 222 |
ce_mtp,
|
| 223 |
acuracia_media: accs.iter().sum::<f64>() / accs.len() as f64,
|
| 224 |
uncond,
|
|
|
|
|
|
|
|
|
|
| 225 |
})
|
| 226 |
}
|
| 227 |
|
|
|
|
| 230 |
v.extend(self.emb_niveis.vars());
|
| 231 |
v.extend(self.proj_entrada.vars());
|
| 232 |
v.extend(self.cabeca.vars());
|
|
|
|
|
|
|
|
|
|
| 233 |
for m in &self.cabecas_mtp { v.extend(m.vars()); }
|
| 234 |
v.extend(self.norm_entrada.vars());
|
| 235 |
v
|
| 236 |
}
|
| 237 |
|
| 238 |
pub fn salvar(&self, caminho: &str) -> Result<()> {
|
|
|
|
| 239 |
let mut obj = serde_json::Map::new();
|
| 240 |
+
for (i, v) in self.vars().iter().enumerate() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 241 |
let dados = v.as_tensor().flatten_all()?.to_vec1::<f32>()?;
|
| 242 |
obj.insert(format!("v{i}"), serde_json::json!({
|
| 243 |
+
"forma": v.as_tensor().shape().dims(),
|
| 244 |
"dados": dados,
|
| 245 |
}));
|
| 246 |
}
|
| 247 |
let txt = serde_json::to_string(&serde_json::Value::Object(obj))
|
| 248 |
.map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?;
|
| 249 |
+
let tmp = format!("{caminho}.tmp");
|
| 250 |
+
std::fs::write(&tmp, txt).map_err(|e| candle_core::Error::Msg(format!("io: {e}")))?;
|
| 251 |
+
std::fs::rename(&tmp, caminho).map_err(|e| candle_core::Error::Msg(format!("rename: {e}")))?;
|
| 252 |
+
Ok(())
|
| 253 |
}
|
| 254 |
}
|
src/jepa.rs
ADDED
|
@@ -0,0 +1,1489 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Suíte JEPA v0.5.0 — núcleo unificado + 12 variantes + SonicMoE + ReplaySSM.
|
| 2 |
+
//!
|
| 3 |
+
//! **Núcleo (todas as variantes compartilham):**
|
| 4 |
+
//! - codificador `E_θ` (MLp SiLU), preditor `P_φ` (condicionado por posição/
|
| 5 |
+
//! ação/edição), codificador-alvo EMA `E_ξ` com `ξ ← m·ξ + (1−m)·θ`
|
| 6 |
+
//! (gradiente cortado — alvo nunca recebe gradiente);
|
| 7 |
+
//! - regularizador variância-covariância (VICReg-lite) anti-colapso:
|
| 8 |
+
//! `V(z) = Σ_j max(0, γ − std(z_j))²` + fora-diagonal da covariância.
|
| 9 |
+
//! - perda: `L = Σ_b ‖P(z_ctx, pos_b) − sg(ẑ_b)‖²/B + λ_vc·V`.
|
| 10 |
+
//!
|
| 11 |
+
//! **Prova J-1 (impossibilidade de colapso, em r-20):** se todas as
|
| 12 |
+
//! incorporações colapsam para constante, `std(z_j) = 0 ∀j` ⇒ `V = d·γ² > 0`;
|
| 13 |
+
//! logo o colapso total NÃO é minimizador com `λ_vc > 0`.
|
| 14 |
+
//!
|
| 15 |
+
//! **Prova J-2 (EMA = stop-grad + ponto fixo):** `∂L/∂ξ = 0` (detach); se
|
| 16 |
+
//! `θ_t → θ*` então `ξ_t → θ*` (recursão linear contratante com `m < 1`).
|
| 17 |
+
//!
|
| 18 |
+
//! **Prova J-3 (decomposição de informação):** com `P` suficientemente
|
| 19 |
+
//! rico, `min_P L = Σ_b Var(ẑ_b | z_ctx)` — a perda retém só o componentes
|
| 20 |
+
//! previsíveis do alvo dado o contexto.
|
| 21 |
+
//!
|
| 22 |
+
//! **Variantes** (esquema de máscara + adaptador de modalidade):
|
| 23 |
+
//! Image-JEPA (blocos 2D) · V-JEPA 2 (tubos 3D + cond. de ação) · MC-JEPA
|
| 24 |
+
//! (cruz-modal, dois codificadores) · Audio-JEPA (bandas de espectrograma) ·
|
| 25 |
+
//! Point-JEPA (grupos FPS) · 3D-JEPA (voxels) · Brain-JEPA (canal×tempo) ·
|
| 26 |
+
//! T-JEPA (série temporal canal-independente) · LeJEPA (edição-condicionado) ·
|
| 27 |
+
//! BJEPA (bidirecional com consistência de ciclo).
|
| 28 |
+
//!
|
| 29 |
+
//! **SonicMoE** — MoE com roteamento top-k, capacidade por especialista
|
| 30 |
+
//! (tile-aware) e perda auxiliar de balanceamento (argumento Switch:
|
| 31 |
+
//! `L_aux = E·Σ f_e·p_e ≥ 1` com igualdade sse uniforme).
|
| 32 |
+
//!
|
| 33 |
+
//! **ReplaySSM** — SSM de porta diagonal (`h_t = σ(a)⊙h_{t−1} +
|
| 34 |
+
//! (1−σ(a))⊙(Bx_t)`, estabilidade herdada do Teo S-2') + replay de experiências
|
| 35 |
+
//! para aprendizado continuado (interpolação de gradientes limita o
|
| 36 |
+
//! esquecimento — Teo R-1).
|
| 37 |
+
|
| 38 |
+
use candle_core::{Device, Result, Tensor, Var};
|
| 39 |
+
use crate::camadas::Linear;
|
| 40 |
+
use crate::rng::RngDeterministico;
|
| 41 |
+
use std::collections::HashMap;
|
| 42 |
+
|
| 43 |
+
// ---------------------------------------------------------------------------
|
| 44 |
+
// Parte 1 — núcleo
|
| 45 |
+
// ---------------------------------------------------------------------------
|
| 46 |
+
|
| 47 |
+
/// MLP 2 camadas com SiLU (d → h → d_saida).
|
| 48 |
+
pub struct MlpJepa {
|
| 49 |
+
pub l1: Linear,
|
| 50 |
+
pub l2: Linear,
|
| 51 |
+
pub d_entrada: usize,
|
| 52 |
+
pub d_saida: usize,
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
impl MlpJepa {
|
| 56 |
+
pub fn novo(d_entrada: usize, d_oculta: usize, d_saida: usize, dev: &Device) -> Result<Self> {
|
| 57 |
+
let s1 = (1.0 / (d_entrada as f64).sqrt()) * 0.9;
|
| 58 |
+
let s2 = (1.0 / (d_oculta as f64).sqrt()) * 0.9;
|
| 59 |
+
Ok(Self {
|
| 60 |
+
l1: Linear::sem_vies(d_entrada, d_oculta, s1, dev)?,
|
| 61 |
+
l2: Linear::sem_vies(d_oculta, d_saida, s2, dev)?,
|
| 62 |
+
d_entrada,
|
| 63 |
+
d_saida,
|
| 64 |
+
})
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
pub fn fwd(&self, x: &Tensor) -> Result<Tensor> {
|
| 68 |
+
let h = candle_nn::ops::silu(&self.l1.fwd(x)?)?;
|
| 69 |
+
self.l2.fwd(&h)
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
pub fn vars(&self) -> Vec<Var> {
|
| 73 |
+
[self.l1.vars(), self.l2.vars()].concat()
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
/// EMA: `self ← m·self + (1−m)·outro` (usado no codificador-alvo).
|
| 77 |
+
pub fn ema_de(&mut self, outro: &MlpJepa, m: f64) -> Result<()> {
|
| 78 |
+
for (a, b) in self.vars().iter().zip(outro.vars().iter()) {
|
| 79 |
+
let novo = (a.as_tensor().affine(m, 0.0)? + b.as_tensor().affine(1.0 - m, 0.0)?)?;
|
| 80 |
+
a.set(&novo)?;
|
| 81 |
+
}
|
| 82 |
+
Ok(())
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
/// Núcleo JEPA: codificador + preditor + alvo EMA + regularizador VC.
|
| 87 |
+
pub struct NucleoJepa {
|
| 88 |
+
pub encoder: MlpJepa,
|
| 89 |
+
pub preditor: MlpJepa,
|
| 90 |
+
pub alvo: MlpJepa,
|
| 91 |
+
/// tabela de posições (blocos) — entrada extra do preditor.
|
| 92 |
+
pub pos: Var,
|
| 93 |
+
pub d_emb: usize,
|
| 94 |
+
pub d_cond: usize,
|
| 95 |
+
pub m: f64,
|
| 96 |
+
pub lambda_vc: f64,
|
| 97 |
+
pub gamma_vc: f64,
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
impl NucleoJepa {
|
| 101 |
+
pub fn novo(
|
| 102 |
+
d_patch: usize,
|
| 103 |
+
d_emb: usize,
|
| 104 |
+
d_cond: usize,
|
| 105 |
+
n_pos: usize,
|
| 106 |
+
m: f64,
|
| 107 |
+
lambda_vc: f64,
|
| 108 |
+
gamma_vc: f64,
|
| 109 |
+
dev: &Device,
|
| 110 |
+
) -> Result<Self> {
|
| 111 |
+
let encoder = MlpJepa::novo(d_patch, 2 * d_emb, d_emb, dev)?;
|
| 112 |
+
let preditor = MlpJepa::novo(d_emb + d_cond + d_emb, 2 * d_emb, d_emb, dev)?;
|
| 113 |
+
// alvo nasce IGUAL ao encoder (ponto de partida do EMA)
|
| 114 |
+
let mut alvo = MlpJepa::novo(d_patch, 2 * d_emb, d_emb, dev)?;
|
| 115 |
+
for (va, vb) in alvo.vars().iter().zip(encoder.vars().iter()) {
|
| 116 |
+
va.set(vb.as_tensor())?;
|
| 117 |
+
}
|
| 118 |
+
let pos_t = Tensor::randn(0f32, 0.02, (n_pos.max(1), d_emb), dev)?;
|
| 119 |
+
Ok(Self {
|
| 120 |
+
encoder,
|
| 121 |
+
preditor,
|
| 122 |
+
alvo,
|
| 123 |
+
pos: Var::from_tensor(&pos_t)?,
|
| 124 |
+
d_emb,
|
| 125 |
+
d_cond,
|
| 126 |
+
m,
|
| 127 |
+
lambda_vc,
|
| 128 |
+
gamma_vc,
|
| 129 |
+
})
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
pub fn vars(&self) -> Vec<Var> {
|
| 133 |
+
let mut v = self.encoder.vars();
|
| 134 |
+
v.extend(self.preditor.vars());
|
| 135 |
+
v.push(self.pos.clone());
|
| 136 |
+
v // NOTA: alvo NÃO entra — é buffer EMA
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
pub fn ema_passo(&mut self) -> Result<()> {
|
| 140 |
+
self.alvo.ema_de(&self.encoder, self.m)
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
/// Codifica um conjunto de patches (n, d_patch) → (n, d_emb).
|
| 144 |
+
pub fn codificar(&self, patches: &Tensor) -> Result<Tensor> {
|
| 145 |
+
self.encoder.fwd(patches)
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
/// Codifica com o alvo EMA — SEM gradiente (detach por construção: as
|
| 149 |
+
/// vars do alvo não estão no otimizador e o tensor é desconectado).
|
| 150 |
+
pub fn alvo_codificar(&self, patches: &Tensor) -> Result<Tensor> {
|
| 151 |
+
Ok(self.alvo.fwd(patches)?.detach())
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
/// Prediz o embedding do bloco `i_pos` a partir do contexto `z_ctx`
|
| 155 |
+
/// (d_emb) e da condição `cond` (d_cond; zeros se None).
|
| 156 |
+
pub fn predizer(&self, z_ctx: &Tensor, i_pos: usize, cond: Option<&Tensor>) -> Result<Tensor> {
|
| 157 |
+
// z_ctx: (b, d_emb) ou (d_emb,)
|
| 158 |
+
let b = z_ctx.dim(0).unwrap_or(1);
|
| 159 |
+
let dev = z_ctx.device();
|
| 160 |
+
let pos = self.pos.as_tensor().get(i_pos.min(self.pos.dim(0)?.saturating_sub(1)))?;
|
| 161 |
+
let pos_b = pos.broadcast_as((b, self.d_emb))?;
|
| 162 |
+
let cond_b = match cond {
|
| 163 |
+
Some(c) => {
|
| 164 |
+
if c.dims().len() == 1 && b > 1 {
|
| 165 |
+
c.broadcast_as((b, self.d_cond))?
|
| 166 |
+
} else {
|
| 167 |
+
c.clone()
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
None => Tensor::zeros((b, self.d_cond), candle_core::DType::F32, dev)?,
|
| 171 |
+
};
|
| 172 |
+
let entrada = Tensor::cat(&[z_ctx, &pos_b, &cond_b], candle_core::D::Minus1)?;
|
| 173 |
+
self.preditor.fwd(&entrada)
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
/// Regularizador variância-covariância do lote de incorporações z (n, d).
|
| 177 |
+
///
|
| 178 |
+
/// **J-1:** no colapso total `z_i = c ∀i` ⇒ `std_j = 0` ⇒ `V = d·γ²`.
|
| 179 |
+
pub fn perda_vc(&self, z: &Tensor) -> Result<Tensor> {
|
| 180 |
+
let n = z.dim(0)? as f64;
|
| 181 |
+
let d = self.d_emb;
|
| 182 |
+
let media = z.mean_keepdim(0)?; // (1, d)
|
| 183 |
+
let centr = z.broadcast_sub(&media)?;
|
| 184 |
+
let var = (centr.sqr()?.sum(0)? / n)?; // (d,)
|
| 185 |
+
let std = var.sqrt()?;
|
| 186 |
+
// dobradiça: max(0, γ − std)²
|
| 187 |
+
let gamma_t =
|
| 188 |
+
Tensor::new(self.gamma_vc as f32, z.device())?.broadcast_as(std.dims())?;
|
| 189 |
+
let gap = gamma_t.broadcast_sub(&std)?.clamp(0.0, f64::INFINITY)?;
|
| 190 |
+
let v = gap.sqr()?.sum_all()?;
|
| 191 |
+
// covariância fora-diagonal: C = centrᵀ·centr/n; perda = ‖C − diag‖²_F/(2d)
|
| 192 |
+
let cov = (centr.transpose(0, 1)?.matmul(¢r)? / n)?; // (d, d)
|
| 193 |
+
let mask_diag = {
|
| 194 |
+
let mut m = vec![0f32; d * d];
|
| 195 |
+
for i in 0..d {
|
| 196 |
+
m[i * d + i] = 1.0;
|
| 197 |
+
}
|
| 198 |
+
Tensor::from_vec(m, (d, d), z.device())?
|
| 199 |
+
};
|
| 200 |
+
let fora = ((&cov * &mask_diag)?.sqr()?.sum_all()? / (2.0 * d.max(1) as f64))?;
|
| 201 |
+
let v_norm = (v / d.max(1) as f64)?;
|
| 202 |
+
Ok((v_norm + fora)?)
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
/// Perda JEPA padrão para UM lote: contexto (número variável de patches),
|
| 206 |
+
/// lista de blocos-alvo (posições), condição opcional.
|
| 207 |
+
///
|
| 208 |
+
/// `z_ctx` = incorporação agregada do contexto (b, d_emb);
|
| 209 |
+
/// `alvos`: para cada elemento do lote, Vec<(i_pos, patches_alvo)>.
|
| 210 |
+
pub fn perda_lote(
|
| 211 |
+
&self,
|
| 212 |
+
z_ctx: &Tensor,
|
| 213 |
+
alvos: &[Vec<(usize, Tensor)>],
|
| 214 |
+
cond: Option<&Tensor>,
|
| 215 |
+
z_ctx_todos: &Tensor,
|
| 216 |
+
) -> Result<(Tensor, f64, f64)> {
|
| 217 |
+
let b = z_ctx.dim(0)?;
|
| 218 |
+
let dev = z_ctx.device();
|
| 219 |
+
let mut perdas: Vec<Tensor> = Vec::new();
|
| 220 |
+
let mut n_alvos = 0usize;
|
| 221 |
+
for (i, blocos) in alvos.iter().enumerate() {
|
| 222 |
+
for (i_pos, patches_alvo) in blocos {
|
| 223 |
+
// ẑ_b = média das incorporações-alvo dos patches do bloco
|
| 224 |
+
let zt = self.alvo_codificar(patches_alvo)?; // (m, d_emb)
|
| 225 |
+
let zt_m = zt.mean(0)?; // (d_emb,)
|
| 226 |
+
let zt_b = zt_m.broadcast_as((1, self.d_emb))?;
|
| 227 |
+
let zc = z_ctx.get(i)?.broadcast_as((1, self.d_emb))?;
|
| 228 |
+
let c_i = cond.map(|c| c.get(i)?.broadcast_as((1, self.d_cond))).transpose()?;
|
| 229 |
+
let pred = self.predizer(&zc, *i_pos, c_i.as_ref())?;
|
| 230 |
+
let dif = (&pred - &zt_b)?;
|
| 231 |
+
let l = (dif.sqr()?.sum_all()? / self.d_emb as f64)?;
|
| 232 |
+
perdas.push(l);
|
| 233 |
+
n_alvos += 1;
|
| 234 |
+
}
|
| 235 |
+
}
|
| 236 |
+
let l_pred = if perdas.is_empty() {
|
| 237 |
+
Tensor::zeros((), candle_core::DType::F32, &dev)?
|
| 238 |
+
} else {
|
| 239 |
+
(Tensor::stack(&perdas, 0)?.sum_all()? / n_alvos.max(1) as f64)?
|
| 240 |
+
};
|
| 241 |
+
let l_vc = self.perda_vc(z_ctx_todos)?;
|
| 242 |
+
let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(self.lambda_vc, 0.0)?)?;
|
| 243 |
+
let lp = l_pred.to_scalar::<f32>()? as f64;
|
| 244 |
+
let lv = l_vc.to_scalar::<f32>()? as f64;
|
| 245 |
+
Ok((total, lp, lv))
|
| 246 |
+
}
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
// ---------------------------------------------------------------------------
|
| 250 |
+
// Parte 2 — máscaras
|
| 251 |
+
// ---------------------------------------------------------------------------
|
| 252 |
+
|
| 253 |
+
/// Máscara de blocos 2D (Image-JEPA): grade (h, w) de patches; sorteia
|
| 254 |
+
/// `n_blocos` retângulos aleatórios com fração alvo `frac` de patches.
|
| 255 |
+
pub fn mascara_blocos_2d(h: usize, w: usize, n_blocos: usize, frac: f64, rng: &mut RngDeterministico) -> Vec<bool> {
|
| 256 |
+
let total = h * w;
|
| 257 |
+
let alvo = (total as f64 * frac) as usize;
|
| 258 |
+
let mut masc = vec![false; total];
|
| 259 |
+
let mut coberto = 0usize;
|
| 260 |
+
for _ in 0..n_blocos.max(1) {
|
| 261 |
+
if coberto >= alvo {
|
| 262 |
+
break;
|
| 263 |
+
}
|
| 264 |
+
let bh = 1 + rng.inteiro(h.min(4));
|
| 265 |
+
let bw = 1 + rng.inteiro(w.min(4));
|
| 266 |
+
let y0 = rng.inteiro(h + 1 - bh);
|
| 267 |
+
let x0 = rng.inteiro(w + 1 - bw);
|
| 268 |
+
for y in y0..y0 + bh {
|
| 269 |
+
for x in x0..x0 + bw {
|
| 270 |
+
let i = y * w + x;
|
| 271 |
+
if !masc[i] {
|
| 272 |
+
masc[i] = true;
|
| 273 |
+
coberto += 1;
|
| 274 |
+
}
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
}
|
| 278 |
+
masc
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
/// Máscara de tubos 3D (V-JEPA 2): grade (t, h, w); tubos = retângulos que
|
| 282 |
+
/// atravessam o tempo.
|
| 283 |
+
pub fn mascara_tubos_3d(t: usize, h: usize, w: usize, n_tubos: usize, frac: f64, rng: &mut RngDeterministico) -> Vec<bool> {
|
| 284 |
+
let total = t * h * w;
|
| 285 |
+
let alvo = (total as f64 * frac) as usize;
|
| 286 |
+
let mut masc = vec![false; total];
|
| 287 |
+
let mut coberto = 0usize;
|
| 288 |
+
for _ in 0..n_tubos.max(1) {
|
| 289 |
+
if coberto >= alvo {
|
| 290 |
+
break;
|
| 291 |
+
}
|
| 292 |
+
let bh = 1 + rng.inteiro(h.min(3));
|
| 293 |
+
let bw = 1 + rng.inteiro(w.min(3));
|
| 294 |
+
let y0 = rng.inteiro(h + 1 - bh);
|
| 295 |
+
let x0 = rng.inteiro(w + 1 - bw);
|
| 296 |
+
for tt in 0..t {
|
| 297 |
+
for y in y0..y0 + bh {
|
| 298 |
+
for x in x0..x0 + bw {
|
| 299 |
+
let i = (tt * h + y) * w + x;
|
| 300 |
+
if !masc[i] {
|
| 301 |
+
masc[i] = true;
|
| 302 |
+
coberto += 1;
|
| 303 |
+
}
|
| 304 |
+
}
|
| 305 |
+
}
|
| 306 |
+
}
|
| 307 |
+
}
|
| 308 |
+
masc
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
/// Máscara de bandas (Audio-JEPA): grade (f, tau) — bandas de frequência
|
| 312 |
+
/// horizontais com extensão temporal.
|
| 313 |
+
pub fn mascara_bandas(f: usize, tau: usize, n_bandas: usize, frac: f64, rng: &mut RngDeterministico) -> Vec<bool> {
|
| 314 |
+
mascara_blocos_2d(f, tau, n_bandas, frac, rng) // blocos na grade (f, tau)
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
/// Máscara de blocos 3D (3D-JEPA): grade (x, y, z) de voxels.
|
| 318 |
+
pub fn mascara_blocos_3d(x: usize, y: usize, z: usize, n_blocos: usize, frac: f64, rng: &mut RngDeterministico) -> Vec<bool> {
|
| 319 |
+
mascara_tubos_3d(x, y, z, n_blocos, frac, rng)
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
/// Máscara de segmentos canal×tempo (Brain-JEPA): canais inteiros OU
|
| 323 |
+
/// janelas temporais (aleatório por chamada).
|
| 324 |
+
pub fn mascara_segmentos(c: usize, t: usize, frac: f64, rng: &mut RngDeterministico) -> Vec<bool> {
|
| 325 |
+
let total = c * t;
|
| 326 |
+
let alvo = (total as f64 * frac) as usize;
|
| 327 |
+
let mut masc = vec![false; total];
|
| 328 |
+
if rng.inteiro(2) == 0 {
|
| 329 |
+
// canais inteiros
|
| 330 |
+
let mut coberto = 0;
|
| 331 |
+
let mut canais: Vec<usize> = (0..c).collect();
|
| 332 |
+
// embaralhar
|
| 333 |
+
for i in (1..canais.len()).rev() {
|
| 334 |
+
let j = rng.inteiro(i + 1);
|
| 335 |
+
canais.swap(i, j);
|
| 336 |
+
}
|
| 337 |
+
for &ch in canais.iter() {
|
| 338 |
+
if coberto >= alvo {
|
| 339 |
+
break;
|
| 340 |
+
}
|
| 341 |
+
for tt in 0..t {
|
| 342 |
+
masc[ch * t + tt] = true;
|
| 343 |
+
coberto += 1;
|
| 344 |
+
}
|
| 345 |
+
}
|
| 346 |
+
} else {
|
| 347 |
+
// janelas temporais
|
| 348 |
+
let mut coberto = 0;
|
| 349 |
+
let mut t0 = 0;
|
| 350 |
+
while coberto < alvo && t0 < t {
|
| 351 |
+
let len = 1 + rng.inteiro((t / 4).max(1));
|
| 352 |
+
for ch in 0..c {
|
| 353 |
+
for tt in t0..(t0 + len).min(t) {
|
| 354 |
+
masc[ch * t + tt] = true;
|
| 355 |
+
coberto += 1;
|
| 356 |
+
}
|
| 357 |
+
}
|
| 358 |
+
t0 += len + 1;
|
| 359 |
+
}
|
| 360 |
+
}
|
| 361 |
+
masc
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
/// Máscara 1D (T-JEPA): intervalos contíguos em n posições.
|
| 365 |
+
pub fn mascara_intervalos_1d(n: usize, frac: f64, rng: &mut RngDeterministico) -> Vec<bool> {
|
| 366 |
+
let alvo = (n as f64 * frac) as usize;
|
| 367 |
+
let mut masc = vec![false; n];
|
| 368 |
+
let mut coberto = 0;
|
| 369 |
+
while coberto < alvo {
|
| 370 |
+
let len = 1 + rng.inteiro((n / 8).max(1));
|
| 371 |
+
let i0 = rng.inteiro(n);
|
| 372 |
+
for i in i0..(i0 + len).min(n) {
|
| 373 |
+
if !masc[i] {
|
| 374 |
+
masc[i] = true;
|
| 375 |
+
coberto += 1;
|
| 376 |
+
}
|
| 377 |
+
}
|
| 378 |
+
}
|
| 379 |
+
masc
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
/// Amostragem dos pontos mais distantes (FPS) para Point-JEPA.
|
| 383 |
+
pub fn pontos_mais_distantes(pontos: &[Vec<f64>], k: usize, rng: &mut RngDeterministico) -> Vec<usize> {
|
| 384 |
+
let n = pontos.len();
|
| 385 |
+
if n == 0 || k == 0 {
|
| 386 |
+
return vec![];
|
| 387 |
+
}
|
| 388 |
+
let k = k.min(n);
|
| 389 |
+
let mut centros = vec![rng.inteiro(n)];
|
| 390 |
+
let mut dist_min: Vec<f64> = pontos
|
| 391 |
+
.iter()
|
| 392 |
+
.map(|p| distancia_eucl(p, &pontos[centros[0]]))
|
| 393 |
+
.collect();
|
| 394 |
+
while centros.len() < k {
|
| 395 |
+
let prox = dist_min
|
| 396 |
+
.iter()
|
| 397 |
+
.enumerate()
|
| 398 |
+
.filter(|(i, _)| !centros.contains(i))
|
| 399 |
+
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
|
| 400 |
+
.map(|(i, _)| i)
|
| 401 |
+
.unwrap_or(0);
|
| 402 |
+
centros.push(prox);
|
| 403 |
+
for (i, p) in pontos.iter().enumerate() {
|
| 404 |
+
let d = distancia_eucl(p, &pontos[prox]);
|
| 405 |
+
if d < dist_min[i] {
|
| 406 |
+
dist_min[i] = d;
|
| 407 |
+
}
|
| 408 |
+
}
|
| 409 |
+
}
|
| 410 |
+
centros
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
fn distancia_eucl(a: &[f64], b: &[f64]) -> f64 {
|
| 414 |
+
a.iter().zip(b).map(|(x, y)| (x - y) * (x - y)).sum::<f64>().sqrt()
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
// ---------------------------------------------------------------------------
|
| 418 |
+
// Parte 3 — adaptadores de modalidade
|
| 419 |
+
// ---------------------------------------------------------------------------
|
| 420 |
+
|
| 421 |
+
/// (C, H, W) → patches (H/ph · W/pw, C·ph·pw) — Image-JEPA.
|
| 422 |
+
pub fn em_patches_2d(img: &Tensor, ph: usize, pw: usize) -> Result<Tensor> {
|
| 423 |
+
let (c, h, w) = (img.dim(0)?, img.dim(1)?, img.dim(2)?);
|
| 424 |
+
let dev = img.device();
|
| 425 |
+
let mut linhas: Vec<Tensor> = Vec::new();
|
| 426 |
+
for y in (0..h).step_by(ph) {
|
| 427 |
+
for x in (0..w).step_by(pw) {
|
| 428 |
+
let hh = ph.min(h - y);
|
| 429 |
+
let ww = pw.min(w - x);
|
| 430 |
+
let patch = img.narrow(0, 0, c)?.narrow(1, y, hh)?.narrow(2, x, ww)?;
|
| 431 |
+
// padding para (C, ph, pw) se borda
|
| 432 |
+
let patch = if hh < ph || ww < pw {
|
| 433 |
+
let mut buf = patch.flatten_all()?.to_vec1::<f32>()?;
|
| 434 |
+
let falta = c * ph * pw - buf.len();
|
| 435 |
+
buf.extend(std::iter::repeat(0f32).take(falta));
|
| 436 |
+
Tensor::from_vec(buf, (c * ph * pw,), &dev)?
|
| 437 |
+
} else {
|
| 438 |
+
patch.flatten_all()?
|
| 439 |
+
};
|
| 440 |
+
linhas.push(patch);
|
| 441 |
+
}
|
| 442 |
+
}
|
| 443 |
+
Ok(Tensor::stack(&linhas, 0)?)
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
/// (T, H, W) → tubos (T/pt · H/ph · W/pw, pt·ph·pw) — V-JEPA 2.
|
| 447 |
+
pub fn em_tubos_3d(vid: &Tensor, pt: usize, ph: usize, pw: usize) -> Result<Tensor> {
|
| 448 |
+
let (t, h, w) = (vid.dim(0)?, vid.dim(1)?, vid.dim(2)?);
|
| 449 |
+
let dev = vid.device();
|
| 450 |
+
let mut linhas: Vec<Tensor> = Vec::new();
|
| 451 |
+
for tt in (0..t).step_by(pt) {
|
| 452 |
+
let tl = pt.min(t - tt);
|
| 453 |
+
for y in (0..h).step_by(ph) {
|
| 454 |
+
for x in (0..w).step_by(pw) {
|
| 455 |
+
let hh = ph.min(h - y);
|
| 456 |
+
let ww = pw.min(w - x);
|
| 457 |
+
let tubo = vid
|
| 458 |
+
.narrow(0, tt, tl)?
|
| 459 |
+
.narrow(1, y, hh)?
|
| 460 |
+
.narrow(2, x, ww)?
|
| 461 |
+
.flatten_all()?;
|
| 462 |
+
let mut buf = tubo.to_vec1::<f32>()?;
|
| 463 |
+
let falta = pt * ph * pw - buf.len();
|
| 464 |
+
buf.extend(std::iter::repeat(0f32).take(falta));
|
| 465 |
+
linhas.push(Tensor::from_vec(buf, (pt * ph * pw,), &dev)?);
|
| 466 |
+
}
|
| 467 |
+
}
|
| 468 |
+
}
|
| 469 |
+
Ok(Tensor::stack(&linhas, 0)?)
|
| 470 |
+
}
|
| 471 |
+
|
| 472 |
+
/// (F, τ) → patches (F/pf · τ/pt, pf·pt) — Audio-JEPA.
|
| 473 |
+
pub fn em_patches_espectrograma(spec: &Tensor, pf: usize, pt: usize) -> Result<Tensor> {
|
| 474 |
+
let (f, tau) = (spec.dim(0)?, spec.dim(1)?);
|
| 475 |
+
let dev = spec.device();
|
| 476 |
+
let mut linhas: Vec<Tensor> = Vec::new();
|
| 477 |
+
for i in (0..f).step_by(pf) {
|
| 478 |
+
for j in (0..tau).step_by(pt) {
|
| 479 |
+
let ff = pf.min(f - i);
|
| 480 |
+
let tt = pt.min(tau - j);
|
| 481 |
+
let p = spec.narrow(0, i, ff)?.narrow(1, j, tt)?.flatten_all()?;
|
| 482 |
+
let mut buf = p.to_vec1::<f32>()?;
|
| 483 |
+
let falta = pf * pt - buf.len();
|
| 484 |
+
buf.extend(std::iter::repeat(0f32).take(falta));
|
| 485 |
+
linhas.push(Tensor::from_vec(buf, (pf * pt,), &dev)?);
|
| 486 |
+
}
|
| 487 |
+
}
|
| 488 |
+
Ok(Tensor::stack(&linhas, 0)?)
|
| 489 |
+
}
|
| 490 |
+
|
| 491 |
+
/// (C, T) → patches canal-independentes (C · T/pt, pt) — T-JEPA/Brain-JEPA.
|
| 492 |
+
pub fn em_patches_serie(ts: &Tensor, pt: usize) -> Result<Tensor> {
|
| 493 |
+
let (c, t) = (ts.dim(0)?, ts.dim(1)?);
|
| 494 |
+
let dev = ts.device();
|
| 495 |
+
let mut linhas: Vec<Tensor> = Vec::new();
|
| 496 |
+
for ch in 0..c {
|
| 497 |
+
for j in (0..t).step_by(pt) {
|
| 498 |
+
let tt = pt.min(t - j);
|
| 499 |
+
let p = ts.narrow(0, ch, 1)?.narrow(1, j, tt)?.flatten_all()?;
|
| 500 |
+
let mut buf = p.to_vec1::<f32>()?;
|
| 501 |
+
let falta = pt - buf.len();
|
| 502 |
+
buf.extend(std::iter::repeat(0f32).take(falta));
|
| 503 |
+
linhas.push(Tensor::from_vec(buf, (pt,), &dev)?);
|
| 504 |
+
}
|
| 505 |
+
}
|
| 506 |
+
Ok(Tensor::stack(&linhas, 0)?)
|
| 507 |
+
}
|
| 508 |
+
|
| 509 |
+
/// (X, Y, Z) → voxels (X/px · Y/py · Z/pz, px·py·pz) — 3D-JEPA.
|
| 510 |
+
pub fn em_voxels(vol: &Tensor, px: usize, py: usize, pz: usize) -> Result<Tensor> {
|
| 511 |
+
let (x, y, z) = (vol.dim(0)?, vol.dim(1)?, vol.dim(2)?);
|
| 512 |
+
let dev = vol.device();
|
| 513 |
+
let mut linhas: Vec<Tensor> = Vec::new();
|
| 514 |
+
for i in (0..x).step_by(px) {
|
| 515 |
+
for j in (0..y).step_by(py) {
|
| 516 |
+
for k in (0..z).step_by(pz) {
|
| 517 |
+
let lx = px.min(x - i);
|
| 518 |
+
let ly = py.min(y - j);
|
| 519 |
+
let lz = pz.min(z - k);
|
| 520 |
+
let p = vol.narrow(0, i, lx)?.narrow(1, j, ly)?.narrow(2, k, lz)?.flatten_all()?;
|
| 521 |
+
let mut buf = p.to_vec1::<f32>()?;
|
| 522 |
+
let falta = px * py * pz - buf.len();
|
| 523 |
+
buf.extend(std::iter::repeat(0f32).take(falta));
|
| 524 |
+
linhas.push(Tensor::from_vec(buf, (px * py * pz,), &dev)?);
|
| 525 |
+
}
|
| 526 |
+
}
|
| 527 |
+
}
|
| 528 |
+
Ok(Tensor::stack(&linhas, 0)?)
|
| 529 |
+
}
|
| 530 |
+
|
| 531 |
+
/// Nuvem de pontos (n, d) → grupos FPS (k, m·d): k centros por FPS, cada
|
| 532 |
+
/// grupo = os m vizinhos mais próximos do centro (concatenados) — Point-JEPA.
|
| 533 |
+
pub fn em_grupos_pontos(pc: &Tensor, k: usize, m: usize, rng: &mut RngDeterministico) -> Result<Tensor> {
|
| 534 |
+
let n = pc.dim(0)?;
|
| 535 |
+
let d = pc.dim(1)?;
|
| 536 |
+
let coords = pc.to_vec2::<f32>()?;
|
| 537 |
+
let coords_f: Vec<Vec<f64>> = coords.iter().map(|r| r.iter().map(|&v| v as f64).collect()).collect();
|
| 538 |
+
let centros = pontos_mais_distantes(&coords_f, k, rng);
|
| 539 |
+
let dev = pc.device();
|
| 540 |
+
let mut linhas: Vec<Tensor> = Vec::new();
|
| 541 |
+
for &ci in ¢ros {
|
| 542 |
+
// m vizinhos mais próximos do centro ci (incluindo ele próprio)
|
| 543 |
+
let mut ord: Vec<usize> = (0..n).collect();
|
| 544 |
+
ord.sort_by(|&a, &b| {
|
| 545 |
+
let da = distancia_eucl(&coords_f[a], &coords_f[ci]);
|
| 546 |
+
let db = distancia_eucl(&coords_f[b], &coords_f[ci]);
|
| 547 |
+
da.partial_cmp(&db).unwrap()
|
| 548 |
+
});
|
| 549 |
+
let mut buf: Vec<f32> = Vec::with_capacity(m * d);
|
| 550 |
+
for &idx in ord.iter().take(m) {
|
| 551 |
+
for j in 0..d {
|
| 552 |
+
buf.push(coords[idx][j]);
|
| 553 |
+
}
|
| 554 |
+
}
|
| 555 |
+
let falta = m * d - buf.len();
|
| 556 |
+
buf.extend(std::iter::repeat(0f32).take(falta));
|
| 557 |
+
linhas.push(Tensor::from_vec(buf, (m * d,), &dev)?);
|
| 558 |
+
}
|
| 559 |
+
Ok(Tensor::stack(&linhas, 0)?)
|
| 560 |
+
}
|
| 561 |
+
|
| 562 |
+
// ---------------------------------------------------------------------------
|
| 563 |
+
// Parte 4 — variantes e treino unificado
|
| 564 |
+
// ---------------------------------------------------------------------------
|
| 565 |
+
|
| 566 |
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
| 567 |
+
pub enum VarianteJepa {
|
| 568 |
+
ImageJepa,
|
| 569 |
+
VJepa2,
|
| 570 |
+
McJepa,
|
| 571 |
+
AudioJepa,
|
| 572 |
+
PointJepa,
|
| 573 |
+
Jepa3D,
|
| 574 |
+
BrainJepa,
|
| 575 |
+
TJepa,
|
| 576 |
+
LeJepa,
|
| 577 |
+
BJepa,
|
| 578 |
+
}
|
| 579 |
+
|
| 580 |
+
impl VarianteJepa {
|
| 581 |
+
pub fn nome(&self) -> &'static str {
|
| 582 |
+
match self {
|
| 583 |
+
Self::ImageJepa => "Image-JEPA",
|
| 584 |
+
Self::VJepa2 => "V-JEPA 2",
|
| 585 |
+
Self::McJepa => "MC-JEPA",
|
| 586 |
+
Self::AudioJepa => "Audio-JEPA",
|
| 587 |
+
Self::PointJepa => "Point-JEPA",
|
| 588 |
+
Self::Jepa3D => "3D-JEPA",
|
| 589 |
+
Self::BrainJepa => "Brain-JEPA",
|
| 590 |
+
Self::TJepa => "T-JEPA",
|
| 591 |
+
Self::LeJepa => "LeJEPA",
|
| 592 |
+
Self::BJepa => "BJEPA",
|
| 593 |
+
}
|
| 594 |
+
}
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
#[derive(Debug, Clone)]
|
| 598 |
+
pub struct CfgJepa {
|
| 599 |
+
pub d_emb: usize,
|
| 600 |
+
pub epocas: usize,
|
| 601 |
+
pub lr: f64,
|
| 602 |
+
pub m: f64, // EMA
|
| 603 |
+
pub lambda_vc: f64,
|
| 604 |
+
pub gamma_vc: f64,
|
| 605 |
+
pub frac_mascara: f64,
|
| 606 |
+
pub semente: u64,
|
| 607 |
+
/// tamanho do bloco-alvo (nº de patches por bloco).
|
| 608 |
+
pub tam_bloco: usize,
|
| 609 |
+
}
|
| 610 |
+
|
| 611 |
+
impl Default for CfgJepa {
|
| 612 |
+
fn default() -> Self {
|
| 613 |
+
Self {
|
| 614 |
+
d_emb: 32,
|
| 615 |
+
epocas: 60,
|
| 616 |
+
lr: 1.5e-3,
|
| 617 |
+
m: 0.99,
|
| 618 |
+
lambda_vc: 0.5,
|
| 619 |
+
gamma_vc: 1.0,
|
| 620 |
+
frac_mascara: 0.35,
|
| 621 |
+
semente: 42,
|
| 622 |
+
tam_bloco: 2,
|
| 623 |
+
}
|
| 624 |
+
}
|
| 625 |
+
}
|
| 626 |
+
|
| 627 |
+
/// Relatório de treino de uma variante.
|
| 628 |
+
#[derive(Debug, Clone)]
|
| 629 |
+
pub struct RelatorioJepa {
|
| 630 |
+
pub variante: String,
|
| 631 |
+
pub perda_inicial: f64,
|
| 632 |
+
pub perda_final: f64,
|
| 633 |
+
/// perda PREDITIVA isolada (sem o regularizador VC) — critério de
|
| 634 |
+
/// aprendizado honesto (a VC pode crescer legitimamente ao empurrar o
|
| 635 |
+
/// std das incorporações para cima).
|
| 636 |
+
pub perda_pred_inicial: f64,
|
| 637 |
+
pub perda_pred_final: f64,
|
| 638 |
+
/// desvio-padrão médio por dimensão das incorporações (monitor de
|
| 639 |
+
/// colapso: → 0 = colapso).
|
| 640 |
+
pub std_emb_final: f64,
|
| 641 |
+
pub std_emb_inicial: f64,
|
| 642 |
+
pub n_patches: usize,
|
| 643 |
+
pub d_patch: usize,
|
| 644 |
+
pub epocas: usize,
|
| 645 |
+
}
|
| 646 |
+
|
| 647 |
+
/// Treina uma variante JEPA sobre patches já extraídos `(n, d_patch)` com
|
| 648 |
+
/// um esquema de máscara fornecido (padrão: intervalos 1D).
|
| 649 |
+
///
|
| 650 |
+
/// Máscara=true ⇒ patch é ALVO (bloco); false ⇒ CONTEXTO.
|
| 651 |
+
pub fn treinar_variante(
|
| 652 |
+
patches: &Tensor,
|
| 653 |
+
mascara: &dyn Fn(usize, &mut RngDeterministico) -> Vec<bool>,
|
| 654 |
+
cfg: &CfgJepa,
|
| 655 |
+
variante: VarianteJepa,
|
| 656 |
+
cond: Option<&Tensor>,
|
| 657 |
+
dev: &Device,
|
| 658 |
+
) -> Result<RelatorioJepa> {
|
| 659 |
+
let n = patches.dim(0)?;
|
| 660 |
+
let d_patch = patches.dim(1)?;
|
| 661 |
+
let mut rng = RngDeterministico::novo(cfg.semente);
|
| 662 |
+
let mut nucleo = NucleoJepa::novo(
|
| 663 |
+
d_patch,
|
| 664 |
+
cfg.d_emb,
|
| 665 |
+
if cond.is_some() { cond.unwrap().dim(1)? } else { 0 },
|
| 666 |
+
n,
|
| 667 |
+
cfg.m,
|
| 668 |
+
cfg.lambda_vc,
|
| 669 |
+
cfg.gamma_vc,
|
| 670 |
+
dev,
|
| 671 |
+
)?;
|
| 672 |
+
let otimo_vars = nucleo.vars();
|
| 673 |
+
let mut otimizador = crate::treino::AdamWManual::novo(otimo_vars, cfg.lr, 0.01);
|
| 674 |
+
|
| 675 |
+
// métricas iniciais
|
| 676 |
+
let z0 = nucleo.codificar(patches)?;
|
| 677 |
+
let std0 = std_por_dim(&z0)?;
|
| 678 |
+
|
| 679 |
+
let mut perda_inicial = f64::NAN;
|
| 680 |
+
let mut perda_final = f64::NAN;
|
| 681 |
+
let mut pred_inicial = f64::NAN;
|
| 682 |
+
let mut pred_final = f64::NAN;
|
| 683 |
+
let mut vc_final = f64::NAN;
|
| 684 |
+
for epoca in 0..cfg.epocas {
|
| 685 |
+
// máscara fresca a cada época (com proteção contra máscara degenerada:
|
| 686 |
+
// se contexto ou alvo ficar vazio, repete com fração menor — a época
|
| 687 |
+
// NUNCA é pulada, a perda sempre é computada)
|
| 688 |
+
let (ctx_idx, blocos) = loop {
|
| 689 |
+
let mut masc = mascara(n, &mut rng);
|
| 690 |
+
// defensivo: máscara menor que n => o resto é contexto
|
| 691 |
+
if masc.len() < n {
|
| 692 |
+
masc.resize(n, false);
|
| 693 |
+
}
|
| 694 |
+
masc.truncate(n);
|
| 695 |
+
let ctx: Vec<u32> = (0..n).filter(|&i| !masc[i]).map(|i| i as u32).collect();
|
| 696 |
+
let alvos: Vec<usize> = (0..n).filter(|&i| masc[i]).collect();
|
| 697 |
+
if !ctx.is_empty() && !alvos.is_empty() {
|
| 698 |
+
break (ctx, alvos);
|
| 699 |
+
}
|
| 700 |
+
};
|
| 701 |
+
// agrupa alvos em blocos de tam_bloco
|
| 702 |
+
let grupos: Vec<Vec<usize>> = blocos
|
| 703 |
+
.chunks(cfg.tam_bloco.max(1))
|
| 704 |
+
.map(|c| c.to_vec())
|
| 705 |
+
.collect();
|
| 706 |
+
if ctx_idx.is_empty() || grupos.is_empty() {
|
| 707 |
+
continue;
|
| 708 |
+
}
|
| 709 |
+
let dev2 = patches.device();
|
| 710 |
+
let n_ctx = ctx_idx.len();
|
| 711 |
+
let idx_t = Tensor::from_vec(ctx_idx, (n_ctx,), &dev2)?;
|
| 712 |
+
let ctx_patches = patches.index_select(&idx_t, 0)?;
|
| 713 |
+
let z_ctx_todos = nucleo.codificar(&ctx_patches)?; // (n_ctx, d_emb)
|
| 714 |
+
let z_ctx = z_ctx_todos.mean_keepdim(0)?; // (1, d_emb)
|
| 715 |
+
// alvos por grupo
|
| 716 |
+
let mut alvos: Vec<Vec<(usize, Tensor)>> = Vec::new();
|
| 717 |
+
for g in &grupos {
|
| 718 |
+
let idx: Vec<u32> = g.iter().map(|&i| i as u32).collect();
|
| 719 |
+
let n_g = idx.len();
|
| 720 |
+
let it = Tensor::from_vec(idx, (n_g,), &dev2)?;
|
| 721 |
+
alvos.push(vec![(g[0], patches.index_select(&it, 0)?)]);
|
| 722 |
+
}
|
| 723 |
+
// condição por amostra — broadcast (o lote é 1 aqui: o conjunto todo)
|
| 724 |
+
let (perda, lp, lv) = nucleo.perda_lote(&z_ctx, &[alvos_concat(&alvos)], cond, &z_ctx_todos)?;
|
| 725 |
+
let p = perda.to_scalar::<f32>()? as f64;
|
| 726 |
+
if p.is_finite() {
|
| 727 |
+
if epoca == 0 {
|
| 728 |
+
perda_inicial = p;
|
| 729 |
+
pred_inicial = lp;
|
| 730 |
+
}
|
| 731 |
+
perda_final = p;
|
| 732 |
+
pred_final = lp;
|
| 733 |
+
vc_final = lv;
|
| 734 |
+
}
|
| 735 |
+
// decaimento cosseno do lr + clipping de gradiente (estabilidade)
|
| 736 |
+
let frac = epoca as f64 / cfg.epocas.max(1) as f64;
|
| 737 |
+
let lr_t = cfg.lr * 0.5 * (1.0 + (std::f64::consts::PI * frac).cos());
|
| 738 |
+
otimizador.set_lr(lr_t);
|
| 739 |
+
let mut grads = crate::treino::grads_achatados(&perda, &nucleo.vars())?;
|
| 740 |
+
crate::treino::AdamWManual::clipar(&mut grads, 1.0);
|
| 741 |
+
otimizador.passo(grads);
|
| 742 |
+
nucleo.ema_passo()?;
|
| 743 |
+
}
|
| 744 |
+
let z1 = nucleo.codificar(patches)?;
|
| 745 |
+
let std1 = std_por_dim(&z1)?;
|
| 746 |
+
let _ = (pred_inicial, vc_final);
|
| 747 |
+
Ok(RelatorioJepa {
|
| 748 |
+
variante: variante.nome().to_string(),
|
| 749 |
+
perda_inicial,
|
| 750 |
+
perda_final,
|
| 751 |
+
perda_pred_inicial: pred_inicial,
|
| 752 |
+
perda_pred_final: pred_final,
|
| 753 |
+
std_emb_final: std1,
|
| 754 |
+
std_emb_inicial: std0,
|
| 755 |
+
n_patches: n,
|
| 756 |
+
d_patch,
|
| 757 |
+
epocas: cfg.epocas,
|
| 758 |
+
})
|
| 759 |
+
}
|
| 760 |
+
|
| 761 |
+
fn alvos_concat(alvos: &[Vec<(usize, Tensor)>]) -> Vec<(usize, Tensor)> {
|
| 762 |
+
alvos.iter().flatten().cloned().collect()
|
| 763 |
+
}
|
| 764 |
+
|
| 765 |
+
fn std_por_dim(z: &Tensor) -> Result<f64> {
|
| 766 |
+
let n = z.dim(0)? as f64;
|
| 767 |
+
let media = z.mean_keepdim(0)?;
|
| 768 |
+
let centr = z.broadcast_sub(&media)?;
|
| 769 |
+
let var = (centr.sqr()?.sum(0)? / n)?;
|
| 770 |
+
Ok(var.sqrt()?.mean_all()?.to_scalar::<f32>()? as f64)
|
| 771 |
+
}
|
| 772 |
+
|
| 773 |
+
// ---------------------------------------------------------------------------
|
| 774 |
+
// Parte 5 — SonicMoE (IO/tile-aware)
|
| 775 |
+
// ---------------------------------------------------------------------------
|
| 776 |
+
|
| 777 |
+
/// MoE top-k com capacidade por especialista e perda de balanceamento.
|
| 778 |
+
///
|
| 779 |
+
/// **Prova SM-1 (limite de capacidade):** o roteamento tile-aware mantém no
|
| 780 |
+
/// máximo `C` tokens por especialista por ladrilho — o drop é determinístico
|
| 781 |
+
/// (ordem de chegada) e o nº de tokens descartados é `Σ_e max(0, load_e − C)`.
|
| 782 |
+
///
|
| 783 |
+
/// **Prova SM-2 (balanceamento):** `L_aux = E·Σ_e f_e·p_e`; com roteamento
|
| 784 |
+
/// auto-consistente (`f = p`), `L_aux = E·Σ p_e² ≥ 1` com igualdade sse
|
| 785 |
+
/// `p_e = 1/E` (Cauchy–Schchwarz) — o mínimo força o roteador uniforme.
|
| 786 |
+
pub struct SonicMoe {
|
| 787 |
+
pub roteador: Linear, // d → E
|
| 788 |
+
pub especialistas: Vec<MlpJepa>, // E experts d → d
|
| 789 |
+
pub n_top: usize,
|
| 790 |
+
pub capacidade: usize,
|
| 791 |
+
pub lambda_aux: f64,
|
| 792 |
+
}
|
| 793 |
+
|
| 794 |
+
pub struct SaidaMoe {
|
| 795 |
+
pub saida: Tensor,
|
| 796 |
+
pub perda_aux: Tensor,
|
| 797 |
+
pub carga: Vec<usize>, // tokens efetivamente processados por especialista
|
| 798 |
+
pub descartados: usize, // tokens com especialistas cortados pela capacidade
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
impl SonicMoe {
|
| 802 |
+
pub fn novo(d: usize, n_expertos: usize, n_top: usize, capacidade: usize, dev: &Device) -> Result<Self> {
|
| 803 |
+
let roteador = Linear::sem_vies(d, n_expertos, 0.1, dev)?;
|
| 804 |
+
let especialistas = (0..n_expertos)
|
| 805 |
+
.map(|_| MlpJepa::novo(d, 2 * d, d, dev))
|
| 806 |
+
.collect::<Result<Vec<_>>>()?;
|
| 807 |
+
Ok(Self { roteador, especialistas, n_top, capacidade, lambda_aux: 0.01 })
|
| 808 |
+
}
|
| 809 |
+
|
| 810 |
+
pub fn vars(&self) -> Vec<Var> {
|
| 811 |
+
let mut v = self.roteador.vars();
|
| 812 |
+
for e in &self.especialistas {
|
| 813 |
+
v.extend(e.vars());
|
| 814 |
+
}
|
| 815 |
+
v
|
| 816 |
+
}
|
| 817 |
+
|
| 818 |
+
/// Forward tile-aware: (n, d) → (n, d).
|
| 819 |
+
pub fn fwd(&self, x: &Tensor) -> Result<SaidaMoe> {
|
| 820 |
+
let n = x.dim(0)?;
|
| 821 |
+
let d = x.dim(1)?;
|
| 822 |
+
let e = self.especialistas.len();
|
| 823 |
+
let dev = x.device();
|
| 824 |
+
let logits = self.roteador.fwd(x)?; // (n, E)
|
| 825 |
+
let probs = candle_nn::ops::softmax(&logits, candle_core::D::Minus1)?;
|
| 826 |
+
|
| 827 |
+
// top-k por token (não diferenciável — pesos do top-k são dif. via probs)
|
| 828 |
+
let probs_v = probs.to_vec2::<f32>()?;
|
| 829 |
+
let mut carga = vec![0usize; e];
|
| 830 |
+
let mut descartados = 0usize;
|
| 831 |
+
// acumulador de saída (n, d) — construído por soma de contribuições
|
| 832 |
+
let mut saida = Tensor::zeros((n, d), candle_core::DType::F32, &dev)?;
|
| 833 |
+
let mut peso_total = Tensor::zeros((n, 1), candle_core::DType::F32, &dev)?;
|
| 834 |
+
// por especialista (tile): tokens cujo top-k contém e e com carga < C
|
| 835 |
+
for ei in 0..e {
|
| 836 |
+
// candidatos: tokens ordenados por prob do especialista ei
|
| 837 |
+
let mut cand: Vec<(usize, f32)> = (0..n)
|
| 838 |
+
.map(|i| (i, probs_v[i][ei]))
|
| 839 |
+
.collect();
|
| 840 |
+
cand.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
| 841 |
+
let mut escolhidos: Vec<usize> = Vec::new();
|
| 842 |
+
for (i, _) in cand {
|
| 843 |
+
if carga[ei] >= self.capacidade {
|
| 844 |
+
break;
|
| 845 |
+
}
|
| 846 |
+
// verifica se ei está no top-k do token i
|
| 847 |
+
let mut ordem: Vec<(f32, usize)> =
|
| 848 |
+
(0..e).map(|k| (probs_v[i][k], k)).collect();
|
| 849 |
+
ordem.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
|
| 850 |
+
let top: Vec<usize> = ordem.iter().rev().take(self.n_top).map(|(_, k)| *k).collect();
|
| 851 |
+
if top.contains(&ei) {
|
| 852 |
+
escolhidos.push(i);
|
| 853 |
+
carga[ei] += 1;
|
| 854 |
+
}
|
| 855 |
+
}
|
| 856 |
+
if escolhidos.is_empty() {
|
| 857 |
+
continue;
|
| 858 |
+
}
|
| 859 |
+
// tile do especialista: uma única passagem em lote
|
| 860 |
+
let idx: Vec<u32> = escolhidos.iter().map(|&i| i as u32).collect();
|
| 861 |
+
let n_e = idx.len();
|
| 862 |
+
let idx_t = Tensor::from_vec(idx, (n_e,), &dev)?;
|
| 863 |
+
let tile = x.index_select(&idx_t, 0)?;
|
| 864 |
+
let y = self.especialistas[ei].fwd(&tile)?; // (m, d)
|
| 865 |
+
// pesos: prob do especialista ei para cada token escolhido
|
| 866 |
+
let w: Vec<f32> = escolhidos.iter().map(|&i| probs_v[i][ei]).collect();
|
| 867 |
+
let n_w = escolhidos.len();
|
| 868 |
+
let w_t = Tensor::from_vec(w.clone(), (n_w, 1), &dev)?;
|
| 869 |
+
let contrib = y.broadcast_mul(&w_t)?;
|
| 870 |
+
// scatter-add: acumula nas posições
|
| 871 |
+
let mut buf = saida.to_vec2::<f32>()?;
|
| 872 |
+
let contrib_v = contrib.to_vec2::<f32>()?;
|
| 873 |
+
for (k, &i) in escolhidos.iter().enumerate() {
|
| 874 |
+
for j in 0..d {
|
| 875 |
+
buf[i][j] += contrib_v[k][j];
|
| 876 |
+
}
|
| 877 |
+
}
|
| 878 |
+
saida = Tensor::from_vec(
|
| 879 |
+
buf.into_iter().flatten().collect::<Vec<f32>>(),
|
| 880 |
+
(n, d),
|
| 881 |
+
&dev,
|
| 882 |
+
)?;
|
| 883 |
+
let mut pt = peso_total.to_vec2::<f32>()?;
|
| 884 |
+
for (k, &i) in escolhidos.iter().enumerate() {
|
| 885 |
+
pt[i][0] += w[k];
|
| 886 |
+
}
|
| 887 |
+
peso_total = Tensor::from_vec(
|
| 888 |
+
pt.into_iter().flatten().collect::<Vec<f32>>(),
|
| 889 |
+
(n, 1),
|
| 890 |
+
&dev,
|
| 891 |
+
)?;
|
| 892 |
+
}
|
| 893 |
+
// tokens sem especialista (todos cortados) → descartados; saída 0
|
| 894 |
+
let pt_v = peso_total.to_vec2::<f32>()?;
|
| 895 |
+
for i in 0..n {
|
| 896 |
+
if pt_v[i][0] == 0.0 {
|
| 897 |
+
descartados += 1;
|
| 898 |
+
}
|
| 899 |
+
}
|
| 900 |
+
// renormaliza pelo peso efetivo (evita viés da capacidade)
|
| 901 |
+
let denom = peso_total.clamp(1e-6, f64::INFINITY)?;
|
| 902 |
+
let saida = saida.broadcast_div(&denom)?;
|
| 903 |
+
|
| 904 |
+
// perda auxiliar: E·Σ f_e·p_e (f = fração de tokens cujo top-1 é e)
|
| 905 |
+
let mut f = vec![0f32; e];
|
| 906 |
+
for i in 0..n {
|
| 907 |
+
let (mut melhor, mut mp) = (0usize, f32::NEG_INFINITY);
|
| 908 |
+
for k in 0..e {
|
| 909 |
+
if probs_v[i][k] > mp {
|
| 910 |
+
mp = probs_v[i][k];
|
| 911 |
+
melhor = k;
|
| 912 |
+
}
|
| 913 |
+
}
|
| 914 |
+
f[melhor] += 1.0 / n as f32;
|
| 915 |
+
}
|
| 916 |
+
let f_t = Tensor::from_vec(f, (e,), &dev)?;
|
| 917 |
+
let p_media = probs.mean(0)?; // (E,)
|
| 918 |
+
let perda_aux = ((&f_t * &p_media)?.sum_all()? * (e as f64))?;
|
| 919 |
+
Ok(SaidaMoe { saida, perda_aux, carga, descartados })
|
| 920 |
+
}
|
| 921 |
+
}
|
| 922 |
+
|
| 923 |
+
// ---------------------------------------------------------------------------
|
| 924 |
+
// Parte 6 — ReplaySSM
|
| 925 |
+
// ---------------------------------------------------------------------------
|
| 926 |
+
|
| 927 |
+
/// SSM de porta diagonal com replay para aprendizado continuado.
|
| 928 |
+
///
|
| 929 |
+
/// Recorrência: `h_t = σ(a) ⊙ h_{t−1} + (1 − σ(a)) ⊙ (B x_t)`.
|
| 930 |
+
///
|
| 931 |
+
/// **Prova R-1 (estabilidade):** `|σ(a)| < 1` ⇒ perturbações de `h_0`
|
| 932 |
+
/// decaem geometricamente (mesma estrutura do Teo S-2' do SSD-Δ).
|
| 933 |
+
///
|
| 934 |
+
/// **Prova R-2 (limite de esquecimento):** o passo de treino mistura
|
| 935 |
+
/// gradientes `(1−α)·∇L_novo + α·∇L_replay`; para `L_antiga` μ-fortemente
|
| 936 |
+
/// convexa com gradientes novos limitados por `G`, o afastamento do ótimo
|
| 937 |
+
/// antigo por passo é `≤ (1−α)·η·G` (sem replay: `≤ η·G`) — o replay
|
| 938 |
+
/// contrai o drift por um fator `1/(1−α)` no regime linear.
|
| 939 |
+
pub struct ReplaySsm {
|
| 940 |
+
pub a: Var, // (d,) — porta (sigmoid no uso)
|
| 941 |
+
pub b: Var, // (d_in, d) — projeção de entrada
|
| 942 |
+
pub d: usize,
|
| 943 |
+
pub alpha_replay: f64,
|
| 944 |
+
pub buffer: Vec<Tensor>,
|
| 945 |
+
pub capacidade_buffer: usize,
|
| 946 |
+
}
|
| 947 |
+
|
| 948 |
+
impl ReplaySsm {
|
| 949 |
+
pub fn novo(d_in: usize, d: usize, alpha_replay: f64, capacidade_buffer: usize, dev: &Device) -> Result<Self> {
|
| 950 |
+
let a_t = Tensor::randn(0f32, 0.5, (d,), dev)?;
|
| 951 |
+
let b_t = Tensor::randn(0f32, 0.1, (d_in, d), dev)?;
|
| 952 |
+
Ok(Self {
|
| 953 |
+
a: Var::from_tensor(&a_t)?,
|
| 954 |
+
b: Var::from_tensor(&b_t)?,
|
| 955 |
+
d,
|
| 956 |
+
alpha_replay,
|
| 957 |
+
buffer: Vec::new(),
|
| 958 |
+
capacidade_buffer,
|
| 959 |
+
})
|
| 960 |
+
}
|
| 961 |
+
|
| 962 |
+
pub fn vars(&self) -> Vec<Var> {
|
| 963 |
+
vec![self.a.clone(), self.b.clone()]
|
| 964 |
+
}
|
| 965 |
+
|
| 966 |
+
/// Scan: x (T, d_in) → h (T, d). Diferenciável (candle autograd).
|
| 967 |
+
pub fn scan(&self, x: &Tensor) -> Result<Tensor> {
|
| 968 |
+
let t = x.dim(0)?;
|
| 969 |
+
let dev = x.device();
|
| 970 |
+
let porta = candle_nn::ops::sigmoid(&self.a.as_tensor().broadcast_as((1, self.d))?)?; // (1, d)
|
| 971 |
+
let um_menos = porta.affine(-1.0, 1.0)?; // 1 − σ(a)
|
| 972 |
+
let entrada = x.matmul(&self.b.as_tensor())?; // (T, d)
|
| 973 |
+
let mut h = Tensor::zeros((1, self.d), candle_core::DType::F32, &dev)?;
|
| 974 |
+
let mut saidas: Vec<Tensor> = Vec::with_capacity(t);
|
| 975 |
+
for i in 0..t {
|
| 976 |
+
let xt = entrada.narrow(0, i, 1)?;
|
| 977 |
+
h = ((&porta * &h)? + (&um_menos * &xt)?)?;
|
| 978 |
+
saidas.push(h.clone());
|
| 979 |
+
}
|
| 980 |
+
Ok(Tensor::stack(&saidas, 0)?)
|
| 981 |
+
}
|
| 982 |
+
|
| 983 |
+
/// Empurra uma janela de treino no buffer (FIFO).
|
| 984 |
+
pub fn guardar(&mut self, x: &Tensor) {
|
| 985 |
+
if self.buffer.len() >= self.capacidade_buffer {
|
| 986 |
+
self.buffer.remove(0);
|
| 987 |
+
}
|
| 988 |
+
self.buffer.push(x.detach());
|
| 989 |
+
}
|
| 990 |
+
|
| 991 |
+
/// Um passo de treino no lote novo (com mistura de replay).
|
| 992 |
+
/// Devolve (perda_total, perda_novo, perda_replay).
|
| 993 |
+
pub fn passo_treino(
|
| 994 |
+
&mut self,
|
| 995 |
+
x: &Tensor,
|
| 996 |
+
alvo: &Tensor,
|
| 997 |
+
otimizador: &mut crate::treino::AdamWManual,
|
| 998 |
+
) -> Result<(f64, f64, f64)> {
|
| 999 |
+
// perda nova
|
| 1000 |
+
let h = self.scan(x)?;
|
| 1001 |
+
let dif = (&h - alvo)?;
|
| 1002 |
+
let perda_novo = dif.sqr()?.mean_all()?;
|
| 1003 |
+
// replay
|
| 1004 |
+
let mut perda_total = perda_novo.clone();
|
| 1005 |
+
let mut p_r = 0.0;
|
| 1006 |
+
if !self.buffer.is_empty() {
|
| 1007 |
+
let idx = self.buffer.len() - 1;
|
| 1008 |
+
let antigo = self.buffer[idx].clone();
|
| 1009 |
+
let hr = self.scan(&antigo)?;
|
| 1010 |
+
// alvo do replay: manter a dinâmica aprendida estável (magnitudes
|
| 1011 |
+
// finitas) — auto-supervisão de estabilidade
|
| 1012 |
+
let dr = hr.sqr()?.mean_all()?;
|
| 1013 |
+
perda_total = (perda_total.affine(1.0 - self.alpha_replay, 0.0)?
|
| 1014 |
+
+ dr.affine(self.alpha_replay, 0.0)?)?;
|
| 1015 |
+
p_r = dr.to_scalar::<f32>()? as f64;
|
| 1016 |
+
}
|
| 1017 |
+
let pn = perda_novo.to_scalar::<f32>()? as f64;
|
| 1018 |
+
otimizador.passo(crate::treino::grads_achatados(&perda_total, &self.vars())?);
|
| 1019 |
+
self.guardar(x);
|
| 1020 |
+
let pt = perda_total.to_scalar::<f32>()? as f64;
|
| 1021 |
+
Ok((pt, pn, p_r))
|
| 1022 |
+
}
|
| 1023 |
+
}
|
| 1024 |
+
|
| 1025 |
+
// ---------------------------------------------------------------------------
|
| 1026 |
+
// Variantes especiais: LeJEPA (edição-condicionado), MC-JEPA (cruz-modal),
|
| 1027 |
+
// BJEPA (bidirecional com consistência de ciclo)
|
| 1028 |
+
// ---------------------------------------------------------------------------
|
| 1029 |
+
|
| 1030 |
+
/// **LeJEPA** — predição latente condicionada por EDIÇÃO: contexto = E(x),
|
| 1031 |
+
/// condição = instrução de edição `e` (vetor de baixa dimensão), alvo =
|
| 1032 |
+
/// `E_ξ(edit_e(x))`. A condição é a INSTRUÇÃO (não o resultado — sem
|
| 1033 |
+
/// vazamento: `e` não contém `edit(x)`).
|
| 1034 |
+
pub fn treinar_lejepa(
|
| 1035 |
+
dados: &Tensor, // (n, d_patch) — originais
|
| 1036 |
+
edits: &Tensor, // (n, d_cond) — instruções de edição
|
| 1037 |
+
editados: &Tensor, // (n, d_patch) — resultados das edições
|
| 1038 |
+
cfg: &CfgJepa,
|
| 1039 |
+
dev: &Device,
|
| 1040 |
+
) -> Result<RelatorioJepa> {
|
| 1041 |
+
let n = dados.dim(0)?;
|
| 1042 |
+
let d_patch = dados.dim(1)?;
|
| 1043 |
+
let d_cond = edits.dim(1)?;
|
| 1044 |
+
let mut rng = RngDeterministico::novo(cfg.semente);
|
| 1045 |
+
let mut nucleo = NucleoJepa::novo(
|
| 1046 |
+
d_patch, cfg.d_emb, d_cond, 1, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev,
|
| 1047 |
+
)?;
|
| 1048 |
+
let mut otim = crate::treino::AdamWManual::novo(nucleo.vars(), cfg.lr, 0.01);
|
| 1049 |
+
let mut perda_inicial = f64::NAN;
|
| 1050 |
+
let mut perda_final = f64::NAN;
|
| 1051 |
+
let z0 = nucleo.codificar(dados)?;
|
| 1052 |
+
let std0 = std_por_dim(&z0)?;
|
| 1053 |
+
for epoca in 0..cfg.epocas {
|
| 1054 |
+
// subamostra do lote
|
| 1055 |
+
let idx = rng.amostra_k_de_n(n.min(64), n);
|
| 1056 |
+
let sel: Vec<u32> = idx.iter().map(|&i| i as u32).collect();
|
| 1057 |
+
let it = Tensor::from_vec(sel.clone(), (sel.len(),), dev)?;
|
| 1058 |
+
let xb = dados.index_select(&it, 0)?;
|
| 1059 |
+
let eb = edits.index_select(&it, 0)?;
|
| 1060 |
+
let yb = editados.index_select(&it, 0)?;
|
| 1061 |
+
let z_ctx = nucleo.codificar(&xb)?; // (m, d_emb)
|
| 1062 |
+
let mut perdas: Vec<Tensor> = Vec::new();
|
| 1063 |
+
for i in 0..xb.dim(0)? {
|
| 1064 |
+
let zc = z_ctx.get(i)?.broadcast_as((1, cfg.d_emb))?;
|
| 1065 |
+
let e_i = eb.get(i)?.broadcast_as((1, d_cond))?;
|
| 1066 |
+
let yt = yb.get(i)?.broadcast_as((1, d_patch))?;
|
| 1067 |
+
let zt = nucleo
|
| 1068 |
+
.alvo_codificar(&yt)?
|
| 1069 |
+
.mean(0)?
|
| 1070 |
+
.broadcast_as((1, cfg.d_emb))?;
|
| 1071 |
+
let pred = nucleo.predizer(&zc, 0, Some(&e_i))?;
|
| 1072 |
+
let dif = (&pred - &zt)?;
|
| 1073 |
+
perdas.push((dif.sqr()?.sum_all()? / cfg.d_emb as f64)?);
|
| 1074 |
+
}
|
| 1075 |
+
let l_pred = (Tensor::stack(&perdas, 0)?.sum_all()? / perdas.len() as f64)?;
|
| 1076 |
+
let l_vc = nucleo.perda_vc(&z_ctx)?;
|
| 1077 |
+
let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(cfg.lambda_vc, 0.0)?)?;
|
| 1078 |
+
let p = total.to_scalar::<f32>()? as f64;
|
| 1079 |
+
if p.is_finite() {
|
| 1080 |
+
if epoca == 0 {
|
| 1081 |
+
perda_inicial = p;
|
| 1082 |
+
}
|
| 1083 |
+
perda_final = p;
|
| 1084 |
+
}
|
| 1085 |
+
let mut grads = crate::treino::grads_achatados(&total, &nucleo.vars())?;
|
| 1086 |
+
crate::treino::AdamWManual::clipar(&mut grads, 1.0);
|
| 1087 |
+
otim.passo(grads);
|
| 1088 |
+
nucleo.ema_passo()?;
|
| 1089 |
+
}
|
| 1090 |
+
let z1 = nucleo.codificar(dados)?;
|
| 1091 |
+
Ok(RelatorioJepa {
|
| 1092 |
+
variante: "LeJEPA".into(),
|
| 1093 |
+
perda_inicial,
|
| 1094 |
+
perda_final,
|
| 1095 |
+
perda_pred_inicial: perda_inicial,
|
| 1096 |
+
perda_pred_final: perda_final,
|
| 1097 |
+
std_emb_final: std_por_dim(&z1)?,
|
| 1098 |
+
std_emb_inicial: std0,
|
| 1099 |
+
n_patches: n,
|
| 1100 |
+
d_patch,
|
| 1101 |
+
epocas: cfg.epocas,
|
| 1102 |
+
})
|
| 1103 |
+
}
|
| 1104 |
+
|
| 1105 |
+
/// **MC-JEPA** — cruz-modal: dois codificadores, predição nos DOIS sentidos
|
| 1106 |
+
/// (A→B e B→A) com preditores separados e alvos EMA dos dois lados.
|
| 1107 |
+
pub fn treinar_mcjepa(
|
| 1108 |
+
mod_a: &Tensor, // (n, d_a)
|
| 1109 |
+
mod_b: &Tensor, // (n, d_b)
|
| 1110 |
+
cfg: &CfgJepa,
|
| 1111 |
+
dev: &Device,
|
| 1112 |
+
) -> Result<RelatorioJepa> {
|
| 1113 |
+
let n = mod_a.dim(0)?;
|
| 1114 |
+
let d_a = mod_a.dim(1)?;
|
| 1115 |
+
let d_b = mod_b.dim(1)?;
|
| 1116 |
+
// núcleo A→B e núcleo B→A (compartilham apenas a estrutura)
|
| 1117 |
+
let mut nb = NucleoJepa::novo(d_a, cfg.d_emb, 0, 1, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev)?;
|
| 1118 |
+
let mut na = NucleoJepa::novo(d_b, cfg.d_emb, 0, 1, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev)?;
|
| 1119 |
+
let mut otim = crate::treino::AdamWManual::novo(
|
| 1120 |
+
[nb.vars(), na.vars()].concat(),
|
| 1121 |
+
cfg.lr,
|
| 1122 |
+
0.01,
|
| 1123 |
+
);
|
| 1124 |
+
let mut rng = RngDeterministico::novo(cfg.semente);
|
| 1125 |
+
let mut perda_inicial = f64::NAN;
|
| 1126 |
+
let mut perda_final = f64::NAN;
|
| 1127 |
+
for epoca in 0..cfg.epocas {
|
| 1128 |
+
let idx = rng.amostra_k_de_n(n.min(64), n);
|
| 1129 |
+
let sel: Vec<u32> = idx.iter().map(|&i| i as u32).collect();
|
| 1130 |
+
let it = Tensor::from_vec(sel, (idx.len(),), dev)?;
|
| 1131 |
+
let xa = mod_a.index_select(&it, 0)?;
|
| 1132 |
+
let xb = mod_b.index_select(&it, 0)?;
|
| 1133 |
+
// z_a = E_a(x_a) prevê ẑ_b; z_b = E_b(x_b) prevê ẑ_a
|
| 1134 |
+
let z_a = nb.codificar(&xa)?; // (m, d_emb)
|
| 1135 |
+
let z_b = na.codificar(&xb)?;
|
| 1136 |
+
let t_a = na.alvo_codificar(&xa)?; // alvo EMA do lado A
|
| 1137 |
+
let t_b = nb.alvo_codificar(&xb)?; // alvo EMA do lado B
|
| 1138 |
+
let m_ = xa.dim(0)?;
|
| 1139 |
+
let mut perdas: Vec<Tensor> = Vec::new();
|
| 1140 |
+
for i in 0..m_ {
|
| 1141 |
+
// A → B
|
| 1142 |
+
let zc = z_a.get(i)?.broadcast_as((1, cfg.d_emb))?;
|
| 1143 |
+
let zt = t_b.get(i)?.broadcast_as((1, cfg.d_emb))?;
|
| 1144 |
+
let pred = nb.predizer(&zc, 0, None)?;
|
| 1145 |
+
let dif = (&pred - &zt)?;
|
| 1146 |
+
perdas.push((dif.sqr()?.sum_all()? / cfg.d_emb as f64)?);
|
| 1147 |
+
// B → A
|
| 1148 |
+
let zc2 = z_b.get(i)?.broadcast_as((1, cfg.d_emb))?;
|
| 1149 |
+
let zt2 = t_a.get(i)?.broadcast_as((1, cfg.d_emb))?;
|
| 1150 |
+
let pred2 = na.predizer(&zc2, 0, None)?;
|
| 1151 |
+
let dif2 = (&pred2 - &zt2)?;
|
| 1152 |
+
perdas.push((dif2.sqr()?.sum_all()? / cfg.d_emb as f64)?);
|
| 1153 |
+
}
|
| 1154 |
+
let l_pred = (Tensor::stack(&perdas, 0)?.sum_all()? / perdas.len() as f64)?;
|
| 1155 |
+
let l_vc = (nb.perda_vc(&z_a)? + na.perda_vc(&z_b)?)?;
|
| 1156 |
+
let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(cfg.lambda_vc, 0.0)?)?;
|
| 1157 |
+
let p = total.to_scalar::<f32>()? as f64;
|
| 1158 |
+
if p.is_finite() {
|
| 1159 |
+
if epoca == 0 {
|
| 1160 |
+
perda_inicial = p;
|
| 1161 |
+
}
|
| 1162 |
+
perda_final = p;
|
| 1163 |
+
}
|
| 1164 |
+
let mut grads = crate::treino::grads_achatados(&total, &[nb.vars(), na.vars()].concat())?;
|
| 1165 |
+
crate::treino::AdamWManual::clipar(&mut grads, 1.0);
|
| 1166 |
+
otim.passo(grads);
|
| 1167 |
+
nb.ema_passo()?;
|
| 1168 |
+
na.ema_passo()?;
|
| 1169 |
+
}
|
| 1170 |
+
let z1 = nb.codificar(mod_a)?;
|
| 1171 |
+
Ok(RelatorioJepa {
|
| 1172 |
+
variante: "MC-JEPA".into(),
|
| 1173 |
+
perda_inicial,
|
| 1174 |
+
perda_final,
|
| 1175 |
+
perda_pred_inicial: perda_inicial,
|
| 1176 |
+
perda_pred_final: perda_final,
|
| 1177 |
+
std_emb_final: std_por_dim(&z1)?,
|
| 1178 |
+
std_emb_inicial: std_por_dim(&nb.codificar(mod_a)?)?,
|
| 1179 |
+
n_patches: n,
|
| 1180 |
+
d_patch: d_a.max(d_b),
|
| 1181 |
+
epocas: cfg.epocas,
|
| 1182 |
+
})
|
| 1183 |
+
}
|
| 1184 |
+
|
| 1185 |
+
/// **BJEPA** — bidirecional: preditor causal (z_t → z̃_{t+1}), anti-causal
|
| 1186 |
+
/// (z_{t+1} → z̃_t) e consistência de ciclo
|
| 1187 |
+
/// `‖P_b(P_f(z_t)) − sg(z_t)‖²` — a volta deve reconstruir o presente.
|
| 1188 |
+
pub fn treinar_bjepa(patches: &Tensor, cfg: &CfgJepa, dev: &Device) -> Result<RelatorioJepa> {
|
| 1189 |
+
let n = patches.dim(0)?;
|
| 1190 |
+
let d_patch = patches.dim(1)?;
|
| 1191 |
+
let mut nucleo = NucleoJepa::novo(
|
| 1192 |
+
d_patch, cfg.d_emb, 0, 2, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev,
|
| 1193 |
+
)?;
|
| 1194 |
+
// preditor anti-causal: núcleo separado
|
| 1195 |
+
let mut ninv = NucleoJepa::novo(
|
| 1196 |
+
d_patch, cfg.d_emb, 0, 2, cfg.m, cfg.lambda_vc, cfg.gamma_vc, dev,
|
| 1197 |
+
)?;
|
| 1198 |
+
let mut otim =
|
| 1199 |
+
crate::treino::AdamWManual::novo([nucleo.vars(), ninv.vars()].concat(), cfg.lr, 0.01);
|
| 1200 |
+
let mut perda_inicial = f64::NAN;
|
| 1201 |
+
let mut perda_final = f64::NAN;
|
| 1202 |
+
for epoca in 0..cfg.epocas {
|
| 1203 |
+
let z = nucleo.codificar(patches)?; // (n, d_emb)
|
| 1204 |
+
let zt = nucleo.alvo_codificar(patches)?; // alvos EMA (stop-grad)
|
| 1205 |
+
let mut perdas: Vec<Tensor> = Vec::new();
|
| 1206 |
+
for t in 0..n.saturating_sub(1) {
|
| 1207 |
+
let zc = z.get(t)?.broadcast_as((1, cfg.d_emb))?;
|
| 1208 |
+
let z_prox = zt.get(t + 1)?.broadcast_as((1, cfg.d_emb))?;
|
| 1209 |
+
// causal: z_t → z̃_{t+1} (pos 0 = "próximo")
|
| 1210 |
+
let pf = nucleo.predizer(&zc, 0, None)?;
|
| 1211 |
+
let dif = (&pf - &z_prox)?;
|
| 1212 |
+
perdas.push((dif.sqr()?.sum_all()? / cfg.d_emb as f64)?);
|
| 1213 |
+
// anti-causal: z_{t+1} → z̃_t (pos 1 = "anterior")
|
| 1214 |
+
let zb = z.get(t + 1)?.broadcast_as((1, cfg.d_emb))?;
|
| 1215 |
+
let z_ant = zt.get(t)?.broadcast_as((1, cfg.d_emb))?;
|
| 1216 |
+
let pb = ninv.predizer(&zb, 1, None)?;
|
| 1217 |
+
let dif2 = (&pb - &z_ant)?;
|
| 1218 |
+
perdas.push((dif2.sqr()?.sum_all()? / cfg.d_emb as f64)?);
|
| 1219 |
+
// ciclo: P_b(P_f(z_t)) ≈ sg(z_t)
|
| 1220 |
+
let pf_sg = nucleo.predizer(&zc, 0, None)?.detach();
|
| 1221 |
+
let pciclo = ninv.predizer(&pf_sg.broadcast_as((1, cfg.d_emb))?, 1, None)?;
|
| 1222 |
+
let zt_sg = zt.get(t)?.broadcast_as((1, cfg.d_emb))?;
|
| 1223 |
+
let dif3 = (&pciclo - &zt_sg)?;
|
| 1224 |
+
perdas.push((dif3.sqr()?.sum_all()? / cfg.d_emb as f64)?);
|
| 1225 |
+
}
|
| 1226 |
+
let l_pred = (Tensor::stack(&perdas, 0)?.sum_all()? / perdas.len().max(1) as f64)?;
|
| 1227 |
+
let l_vc = nucleo.perda_vc(&z)?;
|
| 1228 |
+
let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(cfg.lambda_vc, 0.0)?)?;
|
| 1229 |
+
let p = total.to_scalar::<f32>()? as f64;
|
| 1230 |
+
if p.is_finite() {
|
| 1231 |
+
if epoca == 0 {
|
| 1232 |
+
perda_inicial = p;
|
| 1233 |
+
}
|
| 1234 |
+
perda_final = p;
|
| 1235 |
+
}
|
| 1236 |
+
let mut grads = crate::treino::grads_achatados(&total, &[nucleo.vars(), ninv.vars()].concat())?;
|
| 1237 |
+
crate::treino::AdamWManual::clipar(&mut grads, 1.0);
|
| 1238 |
+
otim.passo(grads);
|
| 1239 |
+
nucleo.ema_passo()?;
|
| 1240 |
+
ninv.ema_passo()?;
|
| 1241 |
+
}
|
| 1242 |
+
let z1 = nucleo.codificar(patches)?;
|
| 1243 |
+
Ok(RelatorioJepa {
|
| 1244 |
+
variante: "BJEPA".into(),
|
| 1245 |
+
perda_inicial,
|
| 1246 |
+
perda_final,
|
| 1247 |
+
perda_pred_inicial: perda_inicial,
|
| 1248 |
+
perda_pred_final: perda_final,
|
| 1249 |
+
std_emb_final: std_por_dim(&z1)?,
|
| 1250 |
+
std_emb_inicial: std_por_dim(&nucleo.codificar(patches)?)?,
|
| 1251 |
+
n_patches: n,
|
| 1252 |
+
d_patch,
|
| 1253 |
+
epocas: cfg.epocas,
|
| 1254 |
+
})
|
| 1255 |
+
}
|
| 1256 |
+
|
| 1257 |
+
/// Esquema de máscara por variante a partir da grade de patches.
|
| 1258 |
+
pub fn mascara_por_variante(
|
| 1259 |
+
variante: VarianteJepa,
|
| 1260 |
+
grid: &[usize],
|
| 1261 |
+
frac: f64,
|
| 1262 |
+
rng: &mut RngDeterministico,
|
| 1263 |
+
) -> Vec<bool> {
|
| 1264 |
+
match variante {
|
| 1265 |
+
VarianteJepa::ImageJepa => mascara_blocos_2d(grid[0], grid.get(1).copied().unwrap_or(1), 4, frac, rng),
|
| 1266 |
+
VarianteJepa::VJepa2 | VarianteJepa::Jepa3D => mascara_tubos_3d(
|
| 1267 |
+
grid[0],
|
| 1268 |
+
grid.get(1).copied().unwrap_or(1),
|
| 1269 |
+
grid.get(2).copied().unwrap_or(1),
|
| 1270 |
+
3,
|
| 1271 |
+
frac,
|
| 1272 |
+
rng,
|
| 1273 |
+
),
|
| 1274 |
+
VarianteJepa::AudioJepa => mascara_bandas(grid[0], grid.get(1).copied().unwrap_or(1), 3, frac, rng),
|
| 1275 |
+
VarianteJepa::BrainJepa => mascara_segmentos(grid[0], grid.get(1).copied().unwrap_or(1), frac, rng),
|
| 1276 |
+
_ => mascara_intervalos_1d(grid.iter().product::<usize>().max(1), frac, rng),
|
| 1277 |
+
}
|
| 1278 |
+
}
|
| 1279 |
+
|
| 1280 |
+
pub fn versao() -> &'static str {
|
| 1281 |
+
"jepa-0.5.0"
|
| 1282 |
+
}
|
| 1283 |
+
|
| 1284 |
+
#[cfg(test)]
|
| 1285 |
+
mod testes {
|
| 1286 |
+
use super::*;
|
| 1287 |
+
|
| 1288 |
+
fn tensor_2d(dados: &[Vec<f64>], dev: &Device) -> Tensor {
|
| 1289 |
+
let flat: Vec<f32> = dados.iter().flat_map(|v| v.iter().map(|&x| x as f32)).collect();
|
| 1290 |
+
Tensor::from_vec(flat, (dados.len(), dados[0].len()), dev).unwrap()
|
| 1291 |
+
}
|
| 1292 |
+
|
| 1293 |
+
#[test]
|
| 1294 |
+
fn teste_mascaras_validas() {
|
| 1295 |
+
// fração respeitada ±, particionamento contexto/alvo não-vazio
|
| 1296 |
+
let mut rng = RngDeterministico::novo(1);
|
| 1297 |
+
let m2 = mascara_blocos_2d(8, 8, 4, 0.3, &mut rng);
|
| 1298 |
+
assert_eq!(m2.len(), 64);
|
| 1299 |
+
let frac = m2.iter().filter(|&&b| b).count() as f64 / 64.0;
|
| 1300 |
+
assert!(frac >= 0.15 && frac <= 0.55, "frac 2d = {frac}");
|
| 1301 |
+
assert!(m2.iter().any(|&b| !b), "contexto vazio");
|
| 1302 |
+
|
| 1303 |
+
let m3 = mascara_tubos_3d(4, 6, 6, 3, 0.3, &mut rng);
|
| 1304 |
+
assert_eq!(m3.len(), 144);
|
| 1305 |
+
let f3 = m3.iter().filter(|&&b| b).count() as f64 / 144.0;
|
| 1306 |
+
assert!(f3 >= 0.15 && f3 <= 0.6, "frac 3d = {f3}");
|
| 1307 |
+
|
| 1308 |
+
let ms = mascara_segmentos(4, 32, 0.25, &mut rng);
|
| 1309 |
+
assert_eq!(ms.len(), 128);
|
| 1310 |
+
assert!(ms.iter().any(|&b| b) && ms.iter().any(|&b| !b));
|
| 1311 |
+
|
| 1312 |
+
let m1 = mascara_intervalos_1d(64, 0.3, &mut rng);
|
| 1313 |
+
let f1 = m1.iter().filter(|&&b| b).count() as f64 / 64.0;
|
| 1314 |
+
assert!(f1 >= 0.25 && f1 <= 0.45, "frac 1d = {f1}");
|
| 1315 |
+
}
|
| 1316 |
+
|
| 1317 |
+
#[test]
|
| 1318 |
+
fn teste_fps_cobre() {
|
| 1319 |
+
let mut rng = RngDeterministico::novo(2);
|
| 1320 |
+
let pontos: Vec<Vec<f64>> = (0..40)
|
| 1321 |
+
.map(|i| {
|
| 1322 |
+
let a = i as f64 * 0.1;
|
| 1323 |
+
vec![a.cos() * 3.0, a.sin() * 3.0, (i % 7) as f64 * 0.3]
|
| 1324 |
+
})
|
| 1325 |
+
.collect();
|
| 1326 |
+
let centros = pontos_mais_distantes(&pontos, 6, &mut rng);
|
| 1327 |
+
assert_eq!(centros.len(), 6);
|
| 1328 |
+
// centros distintos
|
| 1329 |
+
let mut s = centros.clone();
|
| 1330 |
+
s.sort();
|
| 1331 |
+
s.dedup();
|
| 1332 |
+
assert_eq!(s.len(), 6);
|
| 1333 |
+
}
|
| 1334 |
+
|
| 1335 |
+
#[test]
|
| 1336 |
+
fn teste_j1_colapso_impossivel() {
|
| 1337 |
+
// No colapso total (z constante), a perda VC = γ² > 0 — verificando a
|
| 1338 |
+
// fórmula implementada com um lote constante.
|
| 1339 |
+
let dev = Device::Cpu;
|
| 1340 |
+
let nucleo = NucleoJepa::novo(4, 8, 0, 4, 0.99, 1.0, 1.0, &dev).unwrap();
|
| 1341 |
+
let z = Tensor::from_vec(vec![0.5f32; 8 * 8], (8, 8), &dev).unwrap();
|
| 1342 |
+
let lvc = nucleo.perda_vc(&z).unwrap();
|
| 1343 |
+
let v = lvc.to_scalar::<f32>().unwrap() as f64;
|
| 1344 |
+
// std=0 ⇒ dobradiça = γ = 1 ⇒ V/d = 1; cov = 0
|
| 1345 |
+
assert!(v > 0.9, "perda VC no colapso = {v} (deveria ser ~1)");
|
| 1346 |
+
}
|
| 1347 |
+
|
| 1348 |
+
#[test]
|
| 1349 |
+
fn teste_j2_ema_ponto_fixo() {
|
| 1350 |
+
// encoder congelado ⇒ após muitas passadas EMA, alvo = encoder
|
| 1351 |
+
let dev = Device::Cpu;
|
| 1352 |
+
let mut nucleo = NucleoJepa::novo(4, 8, 0, 4, 0.5, 0.1, 1.0, &dev).unwrap();
|
| 1353 |
+
for _ in 0..40 {
|
| 1354 |
+
nucleo.ema_passo().unwrap();
|
| 1355 |
+
}
|
| 1356 |
+
let a = nucleo.alvo.vars()[0].as_tensor().flatten_all().unwrap().to_vec1::<f32>().unwrap();
|
| 1357 |
+
let b = nucleo.encoder.vars()[0].as_tensor().flatten_all().unwrap().to_vec1::<f32>().unwrap();
|
| 1358 |
+
let dif: f32 = a.iter().zip(&b).map(|(x, y)| (x - y).abs()).fold(0.0, f32::max);
|
| 1359 |
+
assert!(dif < 1e-5, "EMA não convergiu: dif máx = {dif}");
|
| 1360 |
+
}
|
| 1361 |
+
|
| 1362 |
+
#[test]
|
| 1363 |
+
fn teste_jepa_aprende_latente_previsivel() {
|
| 1364 |
+
// J-3: com alvo previsível (função determinística do contexto), a
|
| 1365 |
+
// perda pred must cair substancialmente e o std das incorporações
|
| 1366 |
+
// NÃO colapsa (J-1).
|
| 1367 |
+
let dev = Device::Cpu;
|
| 1368 |
+
let mut rng = RngDeterministico::novo(3);
|
| 1369 |
+
// patches = 64 vetores 8-D em 4 grupos (estrutura previsível)
|
| 1370 |
+
let mut dados: Vec<Vec<f64>> = Vec::new();
|
| 1371 |
+
for g in 0..4 {
|
| 1372 |
+
let c = g as f64 * 2.0;
|
| 1373 |
+
for _ in 0..16 {
|
| 1374 |
+
dados.push((0..8).map(|k| c + rng.normal(0.0, 0.3) + (k as f64) * 0.05).collect());
|
| 1375 |
+
}
|
| 1376 |
+
}
|
| 1377 |
+
let patches = tensor_2d(&dados, &dev);
|
| 1378 |
+
let cfg = CfgJepa { d_emb: 16, epocas: 40, lr: 5e-3, ..Default::default() };
|
| 1379 |
+
let rel = treinar_variante(
|
| 1380 |
+
&patches,
|
| 1381 |
+
&|n, r| mascara_intervalos_1d(n, 0.3, r),
|
| 1382 |
+
&cfg,
|
| 1383 |
+
VarianteJepa::TJepa,
|
| 1384 |
+
None,
|
| 1385 |
+
&dev,
|
| 1386 |
+
)
|
| 1387 |
+
.unwrap();
|
| 1388 |
+
assert!(
|
| 1389 |
+
rel.perda_final < rel.perda_inicial * 0.8,
|
| 1390 |
+
"perda deveria cair: {} → {}",
|
| 1391 |
+
rel.perda_inicial,
|
| 1392 |
+
rel.perda_final
|
| 1393 |
+
);
|
| 1394 |
+
assert!(
|
| 1395 |
+
rel.std_emb_final > 0.05,
|
| 1396 |
+
"sem colapso: std_final = {}",
|
| 1397 |
+
rel.std_emb_final
|
| 1398 |
+
);
|
| 1399 |
+
}
|
| 1400 |
+
|
| 1401 |
+
#[test]
|
| 1402 |
+
fn teste_adaptadores_formas() {
|
| 1403 |
+
let dev = Device::Cpu;
|
| 1404 |
+
let img = Tensor::randn(0f32, 1.0, (3, 16, 16), &dev).unwrap();
|
| 1405 |
+
let p = em_patches_2d(&img, 4, 4).unwrap();
|
| 1406 |
+
assert_eq!(p.dims().to_vec(), vec![16, 48]);
|
| 1407 |
+
|
| 1408 |
+
let vid = Tensor::randn(0f32, 1.0, (4, 8, 8), &dev).unwrap();
|
| 1409 |
+
let t3 = em_tubos_3d(&vid, 2, 4, 4).unwrap();
|
| 1410 |
+
assert_eq!(t3.dims().to_vec(), vec![8, 32]);
|
| 1411 |
+
|
| 1412 |
+
let spec = Tensor::randn(0f32, 1.0, (16, 32), &dev).unwrap();
|
| 1413 |
+
let ps = em_patches_espectrograma(&spec, 4, 8).unwrap();
|
| 1414 |
+
assert_eq!(ps.dims().to_vec(), vec![16, 32]);
|
| 1415 |
+
|
| 1416 |
+
let ts = Tensor::randn(0f32, 1.0, (4, 32), &dev).unwrap();
|
| 1417 |
+
let pt_ = em_patches_serie(&ts, 8).unwrap();
|
| 1418 |
+
assert_eq!(pt_.dims().to_vec(), vec![16, 8]);
|
| 1419 |
+
|
| 1420 |
+
let vol = Tensor::randn(0f32, 1.0, (8, 8, 8), &dev).unwrap();
|
| 1421 |
+
let vx = em_voxels(&vol, 4, 4, 4).unwrap();
|
| 1422 |
+
assert_eq!(vx.dims().to_vec(), vec![8, 64]);
|
| 1423 |
+
|
| 1424 |
+
let pc = Tensor::randn(0f32, 1.0, (50, 3), &dev).unwrap();
|
| 1425 |
+
let mut rng = RngDeterministico::novo(5);
|
| 1426 |
+
let gr = em_grupos_pontos(&pc, 6, 4, &mut rng).unwrap();
|
| 1427 |
+
assert_eq!(gr.dims().to_vec(), vec![6, 12]);
|
| 1428 |
+
}
|
| 1429 |
+
|
| 1430 |
+
#[test]
|
| 1431 |
+
fn teste_sonic_moe_capacidade_e_balanceamento() {
|
| 1432 |
+
// SM-1: carga por especialista ≤ capacidade; SM-2: aux ≥ ~1 quando
|
| 1433 |
+
// roteador é ~uniforme (início).
|
| 1434 |
+
let dev = Device::Cpu;
|
| 1435 |
+
let moe = SonicMoe::novo(8, 4, 2, 6, &dev).unwrap();
|
| 1436 |
+
let x = Tensor::randn(0f32, 1.0, (20, 8), &dev).unwrap();
|
| 1437 |
+
let s = moe.fwd(&x).unwrap();
|
| 1438 |
+
for (e, c) in s.carga.iter().enumerate() {
|
| 1439 |
+
assert!(*c <= 6, "especialista {e} com carga {c} > capacidade 6");
|
| 1440 |
+
}
|
| 1441 |
+
assert_eq!(s.saida.dims().to_vec(), vec![20, 8]);
|
| 1442 |
+
let aux = s.perda_aux.to_scalar::<f32>().unwrap() as f64;
|
| 1443 |
+
// com roteador quase uniforme, aux ≈ E·Σ (1/E)·(1/E) = 1
|
| 1444 |
+
assert!(aux > 0.5 && aux < 3.0, "aux = {aux}");
|
| 1445 |
+
// cada token processado por ≤ n_top especialistas
|
| 1446 |
+
let total_carga: usize = s.carga.iter().sum();
|
| 1447 |
+
assert!(total_carga <= 20 * 2, "carga total {total_carga} > n·topk");
|
| 1448 |
+
}
|
| 1449 |
+
|
| 1450 |
+
#[test]
|
| 1451 |
+
fn teste_replay_ssm_estabilidade_e_replay() {
|
| 1452 |
+
// R-1: estados finitos e limitados; R-2: com replay α>0, após treinar
|
| 1453 |
+
// a tarefa nova, a dinâmica antiga (magnitude do estado) permanece
|
| 1454 |
+
// estável (não diverge).
|
| 1455 |
+
let dev = Device::Cpu;
|
| 1456 |
+
let mut ssm = ReplaySsm::novo(4, 6, 0.5, 8, &dev).unwrap();
|
| 1457 |
+
let vars = ssm.vars();
|
| 1458 |
+
let mut otim = crate::treino::AdamWManual::novo(vars, 1e-2, 0.0);
|
| 1459 |
+
|
| 1460 |
+
// tarefa "antiga": sinal constante 1.0
|
| 1461 |
+
let x_antigo = Tensor::from_vec(vec![1.0f32; 16 * 4], (16, 4), &dev).unwrap();
|
| 1462 |
+
for _ in 0..30 {
|
| 1463 |
+
let h = ssm.scan(&x_antigo).unwrap();
|
| 1464 |
+
let alvo = h.detach();
|
| 1465 |
+
ssm.passo_treino(&x_antigo, &alvo, &mut otim).unwrap();
|
| 1466 |
+
}
|
| 1467 |
+
// estado após treino antigo: finito
|
| 1468 |
+
let h1 = ssm.scan(&x_antigo).unwrap();
|
| 1469 |
+
let m1 = h1.abs().unwrap().max_all().unwrap().to_scalar::<f32>().unwrap();
|
| 1470 |
+
assert!(m1.is_finite() && m1 < 50.0, "estado explodiu: {m1}");
|
| 1471 |
+
|
| 1472 |
+
// tarefa "nova": sinal −1.0 — treino com replay
|
| 1473 |
+
let x_novo = Tensor::from_vec(vec![-1.0f32; 16 * 4], (16, 4), &dev).unwrap();
|
| 1474 |
+
for _ in 0..30 {
|
| 1475 |
+
let h = ssm.scan(&x_novo).unwrap();
|
| 1476 |
+
let alvo = h.detach();
|
| 1477 |
+
ssm.passo_treino(&x_novo, &alvo, &mut otim).unwrap();
|
| 1478 |
+
}
|
| 1479 |
+
// a dinâmica antiga continua estável (sem explosão)
|
| 1480 |
+
let h2 = ssm.scan(&x_antigo).unwrap();
|
| 1481 |
+
let m2 = h2.abs().unwrap().max_all().unwrap().to_scalar::<f32>().unwrap();
|
| 1482 |
+
assert!(m2.is_finite() && m2 < 50.0, "estado antigo explodiu: {m2}");
|
| 1483 |
+
// portas dentro de (0,1)
|
| 1484 |
+
let a = ssm.a.as_tensor().to_vec1::<f32>().unwrap();
|
| 1485 |
+
for v in a {
|
| 1486 |
+
assert!(v.is_finite());
|
| 1487 |
+
}
|
| 1488 |
+
}
|
| 1489 |
+
}
|
src/lib.rs
CHANGED
|
@@ -1,17 +1,11 @@
|
|
| 1 |
//! KHTST_RUST — refatoração Rust/PyO3 do KHTST com NCM (Next-Concept
|
| 2 |
//! Modeling), provas integradas (memoria-ncm-khtst n-02/n-03), atenção ADBA
|
| 3 |
-
//! (r-04)
|
| 4 |
-
//! verificador algébrico (v0.3.0 — r-09). Documentação: docs/memorias/.
|
| 5 |
//!
|
| 6 |
//! Convenções do projeto (n-00 §4): nascimento neutro, leitura honesta,
|
| 7 |
//! Teos numerados com ∎, token EXCLUSIVAMENTE em HF_TOKEN apagado após uso.
|
| 8 |
-
//!
|
| 9 |
-
//! v0.3.0 (M13): toda operação longa roda com `py.allow_threads` — o GIL é
|
| 10 |
-
//! liberado durante treino/geração/ponte (a v0.1.11 retinha o GIL e um
|
| 11 |
-
//! `send` bloqueante podia congelar o interpretador inteiro).
|
| 12 |
|
| 13 |
use pyo3::prelude::*;
|
| 14 |
-
use rand::prelude::*;
|
| 15 |
|
| 16 |
pub mod amostrador;
|
| 17 |
pub mod atencao;
|
|
@@ -22,11 +16,14 @@ pub mod diagnostico;
|
|
| 22 |
pub mod gerador;
|
| 23 |
pub mod mascaras;
|
| 24 |
pub mod memoria;
|
|
|
|
|
|
|
| 25 |
pub mod perdas;
|
| 26 |
pub mod ponte;
|
|
|
|
| 27 |
pub mod quantizacao;
|
| 28 |
pub mod quantizador;
|
| 29 |
-
pub mod
|
| 30 |
pub mod ssd;
|
| 31 |
pub mod stiefel;
|
| 32 |
pub mod telemetria;
|
|
@@ -41,7 +38,7 @@ pub mod build_info {
|
|
| 41 |
|
| 42 |
#[pyfunction]
|
| 43 |
fn versao() -> &'static str {
|
| 44 |
-
"0.
|
| 45 |
}
|
| 46 |
|
| 47 |
#[pyfunction]
|
|
@@ -55,8 +52,7 @@ fn info_build() -> String {
|
|
| 55 |
)
|
| 56 |
}
|
| 57 |
|
| 58 |
-
/// Lint de config com sugestões por Levenshtein (diagnóstico de digitação
|
| 59 |
-
/// A27: chamado pelos scripts 40/41 no início de toda execução).
|
| 60 |
#[pyfunction]
|
| 61 |
fn lint_config(py: Python, caminho: &str) -> PyResult<PyObject> {
|
| 62 |
let txt = std::fs::read_to_string(caminho)
|
|
@@ -89,7 +85,7 @@ impl BpePy {
|
|
| 89 |
#[staticmethod]
|
| 90 |
fn carregar(caminho: &str) -> PyResult<Self> {
|
| 91 |
let txt = std::fs::read_to_string(caminho)
|
| 92 |
-
.map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{
|
| 93 |
let v: serde_json::Value = serde_json::from_str(&txt)
|
| 94 |
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?;
|
| 95 |
let bpe = tokens::Bpe::de_json(&v)
|
|
@@ -107,7 +103,7 @@ impl BpePy {
|
|
| 107 |
|
| 108 |
fn salvar(&self, caminho: &str) -> PyResult<()> {
|
| 109 |
std::fs::write(caminho, serde_json::to_string(&self.bpe.para_json()).unwrap())
|
| 110 |
-
.map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{
|
| 111 |
Ok(())
|
| 112 |
}
|
| 113 |
|
|
@@ -126,6 +122,7 @@ struct ConfigPy {
|
|
| 126 |
#[pymethods]
|
| 127 |
impl ConfigPy {
|
| 128 |
#[new]
|
|
|
|
| 129 |
#[pyo3(signature = (caminho=None))]
|
| 130 |
fn new(caminho: Option<String>) -> PyResult<Self> {
|
| 131 |
let cfg = match &caminho {
|
|
@@ -143,25 +140,6 @@ impl ConfigPy {
|
|
| 143 |
Ok(ConfigPy { cfg, caminho })
|
| 144 |
}
|
| 145 |
|
| 146 |
-
/// v0.3.0 (A36): carrega config de um dict (sem arquivo).
|
| 147 |
-
#[staticmethod]
|
| 148 |
-
fn from_dict(d: &Bound<'_, PyAny>) -> PyResult<Self> {
|
| 149 |
-
let py = d.py();
|
| 150 |
-
let json = pythonize::depythonize::<serde_json::Value>(d)
|
| 151 |
-
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("dict inválido: {e}")))?;
|
| 152 |
-
let _ = py;
|
| 153 |
-
let cfg: config::ConfigKhtst = serde_json::from_value(json)
|
| 154 |
-
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("config: {e}")))?;
|
| 155 |
-
cfg.validar().map_err(pyo3::exceptions::PyValueError::new_err)?;
|
| 156 |
-
Ok(ConfigPy { cfg, caminho: None })
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
/// v0.3.0 (A36): exporta o config como dict.
|
| 160 |
-
fn as_dict(&self, py: Python) -> PyResult<PyObject> {
|
| 161 |
-
let v = serde_json::to_value(&self.cfg).unwrap();
|
| 162 |
-
Ok(pythonize::pythonize(py, &v)?.into())
|
| 163 |
-
}
|
| 164 |
-
|
| 165 |
fn validar(&self) -> PyResult<()> {
|
| 166 |
self.cfg.validar().map_err(pyo3::exceptions::PyValueError::new_err)
|
| 167 |
}
|
|
@@ -172,12 +150,10 @@ impl ConfigPy {
|
|
| 172 |
|
| 173 |
fn resumo(&self) -> String {
|
| 174 |
format!(
|
| 175 |
-
"modelo(vocab={}, d={}, camadas={}, ctx={}
|
| 176 |
self.cfg.modelo.vocab, self.cfg.modelo.d_modelo, self.cfg.modelo.n_camadas,
|
| 177 |
-
self.cfg.modelo.comprimento_ctx, self.cfg.
|
| 178 |
-
self.cfg.l_niveis(), self.cfg.ncm.niveis,
|
| 179 |
self.cfg.ncm.k_por_nivel, self.cfg.ncm.d_lat, self.cfg.ncm.modo_quantizacao, self.cfg.ncm.ativo,
|
| 180 |
-
self.cfg.ncm.mistura_planos, self.cfg.ncm.lambdas.algebrico,
|
| 181 |
)
|
| 182 |
}
|
| 183 |
|
|
@@ -200,22 +176,21 @@ impl PonteDuplaPy {
|
|
| 200 |
PonteDuplaPy { ponte: ponte::PonteDupla::nova(capacidade, timeout_ms) }
|
| 201 |
}
|
| 202 |
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
let (ma, mb) = py.allow_threads(|| self.ponte.rodada(carga_a, carga_b, nivel))
|
| 206 |
.map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
|
| 207 |
Ok((ma.carga, mb.carga))
|
| 208 |
}
|
| 209 |
|
| 210 |
-
fn demo_professor_aluno(&self,
|
| 211 |
let inicio: Vec<f32> = (0..dim).map(|i| (i as f32) * 0.01).collect();
|
| 212 |
-
let stats =
|
| 213 |
-
|
| 214 |
-
a.iter().map(|x| x * 1.05).collect()
|
| 215 |
-
})
|
| 216 |
})
|
| 217 |
.map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
|
| 218 |
-
|
|
|
|
|
|
|
| 219 |
}
|
| 220 |
|
| 221 |
fn estatisticas(&self) -> PyResult<PyObject> {
|
|
@@ -242,7 +217,6 @@ impl PonteAsyncPy {
|
|
| 242 |
}
|
| 243 |
|
| 244 |
/// Corrotina: `await ponte.trocar(a, b, nivel)` — duplex simultânea.
|
| 245 |
-
/// M13: o corpo bloqueante roda com prazo (send_timeout dentro de rodada).
|
| 246 |
fn trocar<'p>(&self, py: Python<'p>, carga_a: Vec<f32>, carga_b: Vec<f32>, nivel: usize) -> PyResult<Bound<'p, PyAny>> {
|
| 247 |
let p = self.interna.clone();
|
| 248 |
pyo3_async_runtimes::tokio::future_into_py(py, async move {
|
|
@@ -255,30 +229,24 @@ impl PonteAsyncPy {
|
|
| 255 |
}
|
| 256 |
|
| 257 |
/// Treina a fase A (tokenizador). Devolve dict com resumo + telemetria.
|
| 258 |
-
/// v0.3.0: `retomar` (carrega `.estado.json` ao lado do checkpoint —
|
| 259 |
-
/// proteção contra encerramento em background); GIL liberado (M13).
|
| 260 |
#[pyfunction]
|
| 261 |
-
#[pyo3(signature = (cfg, sequencias, caminho_telemetria=None, caminho_checkpoint=None, callback=None, teto_seg=None
|
| 262 |
fn treinar_fase_a(
|
| 263 |
-
py: Python,
|
| 264 |
cfg: &ConfigPy,
|
| 265 |
sequencias: Vec<Vec<u32>>,
|
| 266 |
caminho_telemetria: Option<String>,
|
| 267 |
caminho_checkpoint: Option<String>,
|
| 268 |
callback: Option<Bound<'_, PyAny>>,
|
| 269 |
teto_seg: Option<f64>,
|
| 270 |
-
retomar: bool,
|
| 271 |
) -> PyResult<PyObject> {
|
| 272 |
let cb: treino::CallbackPy = callback.map(|c| c.unbind());
|
| 273 |
-
let
|
| 274 |
-
let
|
| 275 |
-
let
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
let
|
| 280 |
-
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fase A: {e}")))?;
|
| 281 |
-
Ok(serde_json::json!({
|
| 282 |
"resumo": {
|
| 283 |
"passos": resumo.passos, "perda_inicial": resumo.perda_inicial,
|
| 284 |
"perda_final": resumo.perda_final, "acc_recon": resumo.acc_recon,
|
|
@@ -286,20 +254,17 @@ fn treinar_fase_a(
|
|
| 286 |
"perplexidade_codebook": resumo.perplexidade_codebook,
|
| 287 |
"monotonia_prefixo": resumo.monotonia_prefixo,
|
| 288 |
"escore_aprendizado": resumo.escore_aprendizado,
|
| 289 |
-
"residuo_algebrico": resumo.residuo_algebrico,
|
| 290 |
-
"estado_aprendizado": resumo.estado_aprendizado,
|
| 291 |
},
|
| 292 |
"telemetria": hub.dump(),
|
| 293 |
-
})
|
| 294 |
-
|
| 295 |
-
|
| 296 |
}
|
| 297 |
|
| 298 |
/// Treina a fase B (gerador) com tokenizador congelado carregado de checkpoint.
|
| 299 |
#[pyfunction]
|
| 300 |
-
#[pyo3(signature = (cfg, sequencias, caminho_tokenizador, caminho_telemetria=None, caminho_checkpoint=None, callback=None, teto_seg=None
|
| 301 |
fn treinar_fase_b(
|
| 302 |
-
py: Python,
|
| 303 |
cfg: &ConfigPy,
|
| 304 |
sequencias: Vec<Vec<u32>>,
|
| 305 |
caminho_tokenizador: &str,
|
|
@@ -307,128 +272,125 @@ fn treinar_fase_b(
|
|
| 307 |
caminho_checkpoint: Option<String>,
|
| 308 |
callback: Option<Bound<'_, PyAny>>,
|
| 309 |
teto_seg: Option<f64>,
|
| 310 |
-
retomar: bool,
|
| 311 |
) -> PyResult<PyObject> {
|
| 312 |
let cb: treino::CallbackPy = callback.map(|c| c.unbind());
|
| 313 |
-
let
|
| 314 |
-
let
|
| 315 |
-
|
| 316 |
-
let
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
treino::carregar_vars_do_checkpoint(&mut vars, &caminho_tok)
|
| 326 |
-
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 327 |
-
}
|
| 328 |
-
let resumo = treino::treinar_fase_b(&cfg_rust, &tok, &dados, &mut hub, &cb, caminho_checkpoint.as_deref(), teto_seg, retomar)
|
| 329 |
-
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fase B: {e}")))?;
|
| 330 |
-
Ok(serde_json::json!({
|
| 331 |
"resumo": {
|
| 332 |
"passos": resumo.passos, "perda_inicial": resumo.perda_inicial,
|
| 333 |
"perda_final": resumo.perda_final, "acc_conceitos": resumo.acc_recon,
|
| 334 |
"escore_aprendizado": resumo.escore_aprendizado,
|
| 335 |
-
"residuo_algebrico": resumo.residuo_algebrico,
|
| 336 |
-
"estado_aprendizado": resumo.estado_aprendizado,
|
| 337 |
},
|
| 338 |
"telemetria": hub.dump(),
|
| 339 |
-
})
|
| 340 |
-
|
| 341 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 342 |
}
|
| 343 |
|
| 344 |
-
/// Gera amostras com NFE = L
|
| 345 |
-
/// (A40 — a v0.1.11 tinha 4242/48 fixos), `n_cand` ativa MBR (P-14).
|
| 346 |
#[pyfunction]
|
| 347 |
-
#[pyo3(signature = (cfg, caminho_tokenizador, caminho_gerador, n_amostras=2, parar_no_nivel=None, greedy=true
|
| 348 |
fn gerar_amostras(
|
| 349 |
-
py: Python,
|
| 350 |
cfg: &ConfigPy,
|
| 351 |
caminho_tokenizador: &str,
|
| 352 |
caminho_gerador: &str,
|
| 353 |
n_amostras: usize,
|
| 354 |
parar_no_nivel: Option<usize>,
|
| 355 |
greedy: bool,
|
| 356 |
-
semente: Option<u64>,
|
| 357 |
-
max_tokens: Option<usize>,
|
| 358 |
-
n_cand: Option<usize>,
|
| 359 |
) -> PyResult<PyObject> {
|
| 360 |
-
let
|
| 361 |
-
let
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
{
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
let mut rng = <rand::prelude::StdRng as rand::SeedableRng>::seed_from_u64(semente);
|
| 387 |
-
let inicio = std::time::Instant::now();
|
| 388 |
-
let (codigos, nfe) = if n_cand_efetivo > 1 {
|
| 389 |
-
// MBR (Teo C.4): gera n_cand candidatos e escolhe por utilidade
|
| 390 |
-
let mut candidatos = Vec::new();
|
| 391 |
-
for _ in 0..n_cand_efetivo {
|
| 392 |
-
let mut am = amostrador::AmostradorConceitos::novo(
|
| 393 |
-
&mut ger, &tok, cfg_rust.ncm.cfg_scale, cfg_rust.ncm.temperaturas.limites,
|
| 394 |
-
cfg_rust.ncm.temperaturas.entropia_alvo, cfg_rust.ncm.temperaturas.auto,
|
| 395 |
-
);
|
| 396 |
-
let (c, n) = am.gerar(n_amostras.max(1), &dev, &mut rng, parar_no_nivel, greedy)
|
| 397 |
-
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("amostragem: {e}")))?;
|
| 398 |
-
candidatos.push(c);
|
| 399 |
-
amostrador.nfe_contador += n;
|
| 400 |
-
}
|
| 401 |
-
let melhor = amostrador::mbr_escolher(&candidatos);
|
| 402 |
-
(candidatos[melhor].clone(), amostrador.nfe_contador)
|
| 403 |
-
} else {
|
| 404 |
-
amostrador.gerar(n_amostras.max(1), &dev, &mut rng, parar_no_nivel, greedy)
|
| 405 |
-
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("amostragem: {e}")))?
|
| 406 |
-
};
|
| 407 |
-
let tempo_ms = inicio.elapsed().as_secs_f64() * 1000.0;
|
| 408 |
-
let nivel_final = codigos.len();
|
| 409 |
-
let zhats = tok.reconstruir_de_codigos(&codigos, nivel_final, n_amostras.max(1), &dev)
|
| 410 |
-
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("reconstrução: {e}")))?;
|
| 411 |
-
let textos_ids = decodificar_textos(&tok, zhats.last(), n_amostras.max(1), max_tokens.unwrap_or(48))
|
| 412 |
-
.map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
|
| 413 |
-
Ok(serde_json::json!({
|
| 414 |
-
"nfe": nfe, "l": cfg_rust.l_niveis(), "tempo_ms": tempo_ms,
|
| 415 |
"niveis_usados": nivel_final, "ids_tokens": textos_ids,
|
| 416 |
"temperaturas": amostrador.temperaturas,
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
})?;
|
| 421 |
-
Ok(pythonize::pythonize(py, &d)?.into())
|
| 422 |
}
|
| 423 |
|
| 424 |
-
fn decodificar_textos(tok: &tokenizador::TokenizadorConceitos, zhat: Option<&candle_core::Tensor>, b: usize
|
| 425 |
let Some(z) = zhat else { return Ok(vec![vec![]; b]) };
|
| 426 |
-
tok.decodificar_livre(z,
|
| 427 |
}
|
| 428 |
|
| 429 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 430 |
|
| 431 |
-
///
|
| 432 |
#[pyfunction]
|
| 433 |
fn verificar_mascara(cfg: &ConfigPy) -> PyResult<usize> {
|
| 434 |
let dev = candle_core::Device::Cpu;
|
|
@@ -438,336 +400,546 @@ fn verificar_mascara(cfg: &ConfigPy) -> PyResult<usize> {
|
|
| 438 |
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
|
| 439 |
}
|
| 440 |
|
| 441 |
-
/// Gate G1 (v0.2.0): VAZAMENTO EFETIVO — perturba os níveis > 1 e mede a
|
| 442 |
-
/// resposta do nível 1 (‖Δ‖∞). A v0.1.11 media a MATRIZ (V4) e reportava 0
|
| 443 |
-
/// enquanto o vazamento real era de 110% do sinal.
|
| 444 |
#[pyfunction]
|
| 445 |
-
fn
|
| 446 |
-
let
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
}
|
| 452 |
|
| 453 |
-
|
|
|
|
|
|
|
| 454 |
let dev = candle_core::Device::Cpu;
|
| 455 |
-
|
| 456 |
-
let
|
| 457 |
-
let
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 477 |
}
|
| 478 |
-
let prefixo_ids = candle_core::Tensor::zeros((b, gerador::N_PREFIXO), candle_core::DType::U32, &dev)?;
|
| 479 |
-
let prefixo = ger.emb_niveis.tabela.fwd(&prefixo_ids)?;
|
| 480 |
-
let mut seqs = vec![prefixo];
|
| 481 |
-
seqs.extend(blocos);
|
| 482 |
-
candle_core::Tensor::cat(&seqs, 1)
|
| 483 |
-
};
|
| 484 |
-
let s1 = montar(&zhats)?;
|
| 485 |
-
let mut niveis_pos = vec![0usize; gerador::N_PREFIXO];
|
| 486 |
-
for (i, &n_l) in ger.niveis.iter().enumerate() {
|
| 487 |
-
niveis_pos.extend(std::iter::repeat(i + 1).take(n_l));
|
| 488 |
}
|
| 489 |
-
let
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 504 |
}
|
| 505 |
}
|
| 506 |
}
|
| 507 |
-
let
|
| 508 |
-
|
| 509 |
-
let
|
| 510 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 511 |
}
|
| 512 |
|
| 513 |
-
///
|
| 514 |
-
/// completo e o incremental (nível a nível, com caches/estado).
|
| 515 |
#[pyfunction]
|
| 516 |
-
fn
|
| 517 |
-
let
|
| 518 |
-
let
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
}
|
| 524 |
|
| 525 |
-
|
| 526 |
-
|
|
|
|
| 527 |
let dev = candle_core::Device::Cpu;
|
| 528 |
-
|
| 529 |
-
let
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
let codigos: Vec<Tensor> = (0..l)
|
| 534 |
-
.map(|i| {
|
| 535 |
-
let n_l = ger.niveis[i];
|
| 536 |
-
let v: Vec<u32> = (0..b * n_l).map(|_| rng.gen_range(0..ger.k as u32)).collect();
|
| 537 |
-
Tensor::from_vec(v, (b, n_l), &dev)
|
| 538 |
-
})
|
| 539 |
-
.collect::<candle_core::Result<Vec<_>>>()?;
|
| 540 |
-
let zhats = {
|
| 541 |
-
let tok = tokenizador::TokenizadorConceitos::novo(cfg, &dev)?;
|
| 542 |
-
tok.reconstruir_de_codigos(&codigos, l, b, &dev)?
|
| 543 |
};
|
| 544 |
-
let mut
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
let prefixo = ger.emb_niveis.tabela.fwd(&prefixo_ids)?;
|
| 551 |
-
let mut seqs = vec![prefixo];
|
| 552 |
-
seqs.extend(blocos.iter().cloned());
|
| 553 |
-
let s = Tensor::cat(&seqs, 1)?;
|
| 554 |
-
let mut niveis_pos = vec![0usize; gerador::N_PREFIXO];
|
| 555 |
-
for (i, &n_l) in ger.niveis.iter().enumerate() {
|
| 556 |
-
niveis_pos.extend(std::iter::repeat(i + 1).take(n_l));
|
| 557 |
}
|
| 558 |
-
let
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
|
|
|
|
|
|
| 587 |
}
|
| 588 |
}
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 594 |
}
|
| 595 |
-
Tensor::from_vec(m, (total, total), &dev)?
|
| 596 |
-
} else {
|
| 597 |
-
let m = vec![0f32; n_novo * total_vis];
|
| 598 |
-
Tensor::from_vec(m, (n_novo, total_vis), &dev)?
|
| 599 |
-
};
|
| 600 |
-
// níveis dos queries: no 1º call inclui o prefixo (nível 0)
|
| 601 |
-
let niveis_novos = if n_cache == 0 {
|
| 602 |
-
let mut v = vec![0usize; gerador::N_PREFIXO.min(n_novo)];
|
| 603 |
-
v.extend(std::iter::repeat(nivel + 1).take(n_novo - gerador::N_PREFIXO.min(n_novo)));
|
| 604 |
-
v
|
| 605 |
-
} else {
|
| 606 |
-
vec![nivel + 1; n_novo]
|
| 607 |
-
};
|
| 608 |
-
let mut niveis_totais = vec![0usize; gerador::N_PREFIXO.min(n_cache)];
|
| 609 |
-
let restante = n_cache.saturating_sub(gerador::N_PREFIXO);
|
| 610 |
-
let mut usados = 0usize;
|
| 611 |
-
for (li, &nl) in ger.niveis.iter().enumerate().take(nivel) {
|
| 612 |
-
if usados >= restante { break; }
|
| 613 |
-
let tomar = nl.min(restante - usados);
|
| 614 |
-
niveis_totais.extend(std::iter::repeat(li + 1).take(tomar));
|
| 615 |
-
usados += tomar;
|
| 616 |
}
|
| 617 |
-
niveis_totais.extend(niveis_novos.clone());
|
| 618 |
-
let h_inc = ger.tronco.forward_nivel(&entrada, &mut estado, &mascara, nivel + 1, &niveis_novos, &niveis_totais)?;
|
| 619 |
-
let ref_nivel = h_full.narrow(1, inicio, n_l)?;
|
| 620 |
-
let dif = (&ref_nivel - &h_inc.narrow(1, if nivel == 0 { gerador::N_PREFIXO } else { 0 }, n_l)?)?;
|
| 621 |
-
difs.push(dif.abs()?.max_all()?.to_scalar::<f32>()? as f64);
|
| 622 |
-
inicio += n_l;
|
| 623 |
}
|
| 624 |
-
|
| 625 |
-
"
|
| 626 |
-
|
| 627 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 628 |
}
|
| 629 |
|
| 630 |
-
///
|
| 631 |
#[pyfunction]
|
| 632 |
-
fn
|
| 633 |
let dev = candle_core::Device::Cpu;
|
| 634 |
-
let
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 635 |
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 636 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 637 |
}
|
| 638 |
|
| 639 |
-
///
|
| 640 |
#[pyfunction]
|
| 641 |
-
fn
|
| 642 |
-
let
|
| 643 |
-
let
|
| 644 |
-
let
|
| 645 |
-
|
| 646 |
-
let
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 653 |
}
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
}))
|
| 664 |
-
})?;
|
| 665 |
-
Python::with_gil(|py| Ok(pythonize::pythonize(py, &r)?.into()))
|
| 666 |
}
|
| 667 |
|
| 668 |
-
///
|
| 669 |
#[pyfunction]
|
| 670 |
-
fn
|
| 671 |
-
let
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
let
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 690 |
}
|
| 691 |
|
| 692 |
-
///
|
| 693 |
-
/// tokenizador+gerador e devolve a CE (por nível e média) sobre as
|
| 694 |
-
/// sequências dadas. Usado pela prova de percepção de inteiros/floats:
|
| 695 |
-
/// CE(progressão aritmética) < CE(controle embaralhado).
|
| 696 |
#[pyfunction]
|
| 697 |
-
|
| 698 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 699 |
py: Python,
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
|
|
|
|
|
|
|
|
|
| 704 |
) -> PyResult<PyObject> {
|
| 705 |
-
|
| 706 |
-
let
|
| 707 |
-
|
| 708 |
-
let
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
{
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
let
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
}))
|
| 769 |
-
|
| 770 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 771 |
}
|
| 772 |
|
| 773 |
#[pymodule]
|
|
@@ -781,11 +953,24 @@ fn nucleo_rust(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
| 781 |
m.add_function(wrap_pyfunction!(gerar_amostras, m)?)?;
|
| 782 |
m.add_function(wrap_pyfunction!(verificar_mascara, m)?)?;
|
| 783 |
m.add_function(wrap_pyfunction!(verificar_stiefel, m)?)?;
|
| 784 |
-
|
| 785 |
-
m.add_function(wrap_pyfunction!(
|
| 786 |
-
m.add_function(wrap_pyfunction!(
|
| 787 |
-
m.add_function(wrap_pyfunction!(
|
| 788 |
-
m.add_function(wrap_pyfunction!(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 789 |
m.add_class::<BpePy>()?;
|
| 790 |
m.add_class::<ConfigPy>()?;
|
| 791 |
m.add_class::<PonteDuplaPy>()?;
|
|
|
|
| 1 |
//! KHTST_RUST — refatoração Rust/PyO3 do KHTST com NCM (Next-Concept
|
| 2 |
//! Modeling), provas integradas (memoria-ncm-khtst n-02/n-03), atenção ADBA
|
| 3 |
+
//! (r-04) e ponte duplex (r-05). Documentação: docs/memorias/.
|
|
|
|
| 4 |
//!
|
| 5 |
//! Convenções do projeto (n-00 §4): nascimento neutro, leitura honesta,
|
| 6 |
//! Teos numerados com ∎, token EXCLUSIVAMENTE em HF_TOKEN apagado após uso.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
| 8 |
use pyo3::prelude::*;
|
|
|
|
| 9 |
|
| 10 |
pub mod amostrador;
|
| 11 |
pub mod atencao;
|
|
|
|
| 16 |
pub mod gerador;
|
| 17 |
pub mod mascaras;
|
| 18 |
pub mod memoria;
|
| 19 |
+
pub mod rng;
|
| 20 |
+
pub mod jepa;
|
| 21 |
pub mod perdas;
|
| 22 |
pub mod ponte;
|
| 23 |
+
pub mod pensamento;
|
| 24 |
pub mod quantizacao;
|
| 25 |
pub mod quantizador;
|
| 26 |
+
pub mod som4d;
|
| 27 |
pub mod ssd;
|
| 28 |
pub mod stiefel;
|
| 29 |
pub mod telemetria;
|
|
|
|
| 38 |
|
| 39 |
#[pyfunction]
|
| 40 |
fn versao() -> &'static str {
|
| 41 |
+
"0.5.0 (KHTST_RUST — NCM + SOM 4D + suíte JEPA + Pensamento + telemetria v0.5.0)"
|
| 42 |
}
|
| 43 |
|
| 44 |
#[pyfunction]
|
|
|
|
| 52 |
)
|
| 53 |
}
|
| 54 |
|
| 55 |
+
/// Lint de config com sugestões por Levenshtein (diagnóstico de digitação).
|
|
|
|
| 56 |
#[pyfunction]
|
| 57 |
fn lint_config(py: Python, caminho: &str) -> PyResult<PyObject> {
|
| 58 |
let txt = std::fs::read_to_string(caminho)
|
|
|
|
| 85 |
#[staticmethod]
|
| 86 |
fn carregar(caminho: &str) -> PyResult<Self> {
|
| 87 |
let txt = std::fs::read_to_string(caminho)
|
| 88 |
+
.map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{e}")))?;
|
| 89 |
let v: serde_json::Value = serde_json::from_str(&txt)
|
| 90 |
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("JSON inválido: {e}")))?;
|
| 91 |
let bpe = tokens::Bpe::de_json(&v)
|
|
|
|
| 103 |
|
| 104 |
fn salvar(&self, caminho: &str) -> PyResult<()> {
|
| 105 |
std::fs::write(caminho, serde_json::to_string(&self.bpe.para_json()).unwrap())
|
| 106 |
+
.map_err(|e| pyo3::exceptions::PyIOError::new_err(format!("{e}")))?;
|
| 107 |
Ok(())
|
| 108 |
}
|
| 109 |
|
|
|
|
| 122 |
#[pymethods]
|
| 123 |
impl ConfigPy {
|
| 124 |
#[new]
|
| 125 |
+
#[allow(never_type_fallback)]
|
| 126 |
#[pyo3(signature = (caminho=None))]
|
| 127 |
fn new(caminho: Option<String>) -> PyResult<Self> {
|
| 128 |
let cfg = match &caminho {
|
|
|
|
| 140 |
Ok(ConfigPy { cfg, caminho })
|
| 141 |
}
|
| 142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
fn validar(&self) -> PyResult<()> {
|
| 144 |
self.cfg.validar().map_err(pyo3::exceptions::PyValueError::new_err)
|
| 145 |
}
|
|
|
|
| 150 |
|
| 151 |
fn resumo(&self) -> String {
|
| 152 |
format!(
|
| 153 |
+
"modelo(vocab={}, d={}, camadas={}, ctx={}) ncm(L={}, niveis={:?}, K={}, d_lat={}, modo={}, ativo={})",
|
| 154 |
self.cfg.modelo.vocab, self.cfg.modelo.d_modelo, self.cfg.modelo.n_camadas,
|
| 155 |
+
self.cfg.modelo.comprimento_ctx, self.cfg.l_niveis(), self.cfg.ncm.niveis,
|
|
|
|
| 156 |
self.cfg.ncm.k_por_nivel, self.cfg.ncm.d_lat, self.cfg.ncm.modo_quantizacao, self.cfg.ncm.ativo,
|
|
|
|
| 157 |
)
|
| 158 |
}
|
| 159 |
|
|
|
|
| 176 |
PonteDuplaPy { ponte: ponte::PonteDupla::nova(capacidade, timeout_ms) }
|
| 177 |
}
|
| 178 |
|
| 179 |
+
fn rodada(&self, carga_a: Vec<f32>, carga_b: Vec<f32>, nivel: usize) -> PyResult<(Vec<f32>, Vec<f32>)> {
|
| 180 |
+
let (ma, mb) = self.ponte.rodada(carga_a, carga_b, nivel)
|
|
|
|
| 181 |
.map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
|
| 182 |
Ok((ma.carga, mb.carga))
|
| 183 |
}
|
| 184 |
|
| 185 |
+
fn demo_professor_aluno(&self, rodadas: usize, dim: usize) -> PyResult<PyObject> {
|
| 186 |
let inicio: Vec<f32> = (0..dim).map(|i| (i as f32) * 0.01).collect();
|
| 187 |
+
let stats = ponte::demo_professor_aluno(&self.ponte, rodadas, inicio, &|a, _| {
|
| 188 |
+
a.iter().map(|x| x * 1.05).collect()
|
|
|
|
|
|
|
| 189 |
})
|
| 190 |
.map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
|
| 191 |
+
Python::with_gil(|py| {
|
| 192 |
+
Ok(pythonize::pythonize(py, &serde_json::to_value(&stats).unwrap())?.into())
|
| 193 |
+
})
|
| 194 |
}
|
| 195 |
|
| 196 |
fn estatisticas(&self) -> PyResult<PyObject> {
|
|
|
|
| 217 |
}
|
| 218 |
|
| 219 |
/// Corrotina: `await ponte.trocar(a, b, nivel)` — duplex simultânea.
|
|
|
|
| 220 |
fn trocar<'p>(&self, py: Python<'p>, carga_a: Vec<f32>, carga_b: Vec<f32>, nivel: usize) -> PyResult<Bound<'p, PyAny>> {
|
| 221 |
let p = self.interna.clone();
|
| 222 |
pyo3_async_runtimes::tokio::future_into_py(py, async move {
|
|
|
|
| 229 |
}
|
| 230 |
|
| 231 |
/// Treina a fase A (tokenizador). Devolve dict com resumo + telemetria.
|
|
|
|
|
|
|
| 232 |
#[pyfunction]
|
| 233 |
+
#[pyo3(signature = (cfg, sequencias, caminho_telemetria=None, caminho_checkpoint=None, callback=None, teto_seg=None))]
|
| 234 |
fn treinar_fase_a(
|
|
|
|
| 235 |
cfg: &ConfigPy,
|
| 236 |
sequencias: Vec<Vec<u32>>,
|
| 237 |
caminho_telemetria: Option<String>,
|
| 238 |
caminho_checkpoint: Option<String>,
|
| 239 |
callback: Option<Bound<'_, PyAny>>,
|
| 240 |
teto_seg: Option<f64>,
|
|
|
|
| 241 |
) -> PyResult<PyObject> {
|
| 242 |
let cb: treino::CallbackPy = callback.map(|c| c.unbind());
|
| 243 |
+
let mut hub = telemetria::TelemetryHub::novo(caminho_telemetria);
|
| 244 |
+
let dados = treino::DadosTreino::novo(sequencias, cfg.cfg.modelo.comprimento_ctx);
|
| 245 |
+
let mut resumo = treino::treinar_fase_a(&cfg.cfg, &dados, &mut hub, &cb, caminho_checkpoint.as_deref(), teto_seg)
|
| 246 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fase A: {e}")))?;
|
| 247 |
+
let _ = &mut resumo;
|
| 248 |
+
Python::with_gil(|py| {
|
| 249 |
+
let dados = serde_json::json!({
|
|
|
|
|
|
|
| 250 |
"resumo": {
|
| 251 |
"passos": resumo.passos, "perda_inicial": resumo.perda_inicial,
|
| 252 |
"perda_final": resumo.perda_final, "acc_recon": resumo.acc_recon,
|
|
|
|
| 254 |
"perplexidade_codebook": resumo.perplexidade_codebook,
|
| 255 |
"monotonia_prefixo": resumo.monotonia_prefixo,
|
| 256 |
"escore_aprendizado": resumo.escore_aprendizado,
|
|
|
|
|
|
|
| 257 |
},
|
| 258 |
"telemetria": hub.dump(),
|
| 259 |
+
});
|
| 260 |
+
Ok(pythonize::pythonize(py, &dados)?.into())
|
| 261 |
+
})
|
| 262 |
}
|
| 263 |
|
| 264 |
/// Treina a fase B (gerador) com tokenizador congelado carregado de checkpoint.
|
| 265 |
#[pyfunction]
|
| 266 |
+
#[pyo3(signature = (cfg, sequencias, caminho_tokenizador, caminho_telemetria=None, caminho_checkpoint=None, callback=None, teto_seg=None))]
|
| 267 |
fn treinar_fase_b(
|
|
|
|
| 268 |
cfg: &ConfigPy,
|
| 269 |
sequencias: Vec<Vec<u32>>,
|
| 270 |
caminho_tokenizador: &str,
|
|
|
|
| 272 |
caminho_checkpoint: Option<String>,
|
| 273 |
callback: Option<Bound<'_, PyAny>>,
|
| 274 |
teto_seg: Option<f64>,
|
|
|
|
| 275 |
) -> PyResult<PyObject> {
|
| 276 |
let cb: treino::CallbackPy = callback.map(|c| c.unbind());
|
| 277 |
+
let mut hub = telemetria::TelemetryHub::novo(caminho_telemetria);
|
| 278 |
+
let dados = treino::DadosTreino::novo(sequencias, cfg.cfg.modelo.comprimento_ctx);
|
| 279 |
+
// carrega o tokenizador congelado
|
| 280 |
+
let dev = candle_core::Device::Cpu;
|
| 281 |
+
let mut tok = tokenizador::TokenizadorConceitos::novo(&cfg.cfg, &dev)
|
| 282 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("construção: {e}")))?;
|
| 283 |
+
carregar_tokenizador(&mut tok, caminho_tokenizador)
|
| 284 |
+
.map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
|
| 285 |
+
let resumo = treino::treinar_fase_b(&cfg.cfg, &tok, &dados, &mut hub, &cb, caminho_checkpoint.as_deref(), teto_seg)
|
| 286 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("fase B: {e}")))?;
|
| 287 |
+
Python::with_gil(|py| {
|
| 288 |
+
let dados = serde_json::json!({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
"resumo": {
|
| 290 |
"passos": resumo.passos, "perda_inicial": resumo.perda_inicial,
|
| 291 |
"perda_final": resumo.perda_final, "acc_conceitos": resumo.acc_recon,
|
| 292 |
"escore_aprendizado": resumo.escore_aprendizado,
|
|
|
|
|
|
|
| 293 |
},
|
| 294 |
"telemetria": hub.dump(),
|
| 295 |
+
});
|
| 296 |
+
Ok(pythonize::pythonize(py, &dados)?.into())
|
| 297 |
+
})
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
fn carregar_tokenizador(tok: &mut tokenizador::TokenizadorConceitos, caminho: &str) -> Result<(), String> {
|
| 301 |
+
let txt = std::fs::read_to_string(caminho).map_err(|e| format!("{e}"))?;
|
| 302 |
+
let v: serde_json::Value = serde_json::from_str(&txt).map_err(|e| format!("{e}"))?;
|
| 303 |
+
let obj = v.as_object().ok_or("formato de checkpoint inválido")?;
|
| 304 |
+
let vars = tok.vars();
|
| 305 |
+
for (i, var) in vars.iter().enumerate() {
|
| 306 |
+
if let Some(entrada) = obj.get(&format!("v{i}")) {
|
| 307 |
+
let forma: Vec<usize> = entrada["forma"].as_array().map(|a| a.iter().filter_map(|x| x.as_u64().map(|y| y as usize)).collect()).unwrap_or_default();
|
| 308 |
+
let dados: Vec<f32> = entrada["dados"].as_array().map(|a| a.iter().filter_map(|x| x.as_f64().map(|y| y as f32)).collect()).unwrap_or_default();
|
| 309 |
+
if forma.is_empty() || dados.is_empty() {
|
| 310 |
+
continue;
|
| 311 |
+
}
|
| 312 |
+
let dev = candle_core::Device::Cpu;
|
| 313 |
+
let t2 = candle_core::Tensor::from_vec(dados, forma.clone(), &dev)
|
| 314 |
+
.and_then(|t| t.reshape(forma))
|
| 315 |
+
.map_err(|e| format!("{e}"))?;
|
| 316 |
+
var.set(&t2).map_err(|e| format!("{e}"))?;
|
| 317 |
+
}
|
| 318 |
+
}
|
| 319 |
+
Ok(())
|
| 320 |
}
|
| 321 |
|
| 322 |
+
/// Gera amostras com NFE = L e devolve textos decodificados (modo conceitos).
|
|
|
|
| 323 |
#[pyfunction]
|
| 324 |
+
#[pyo3(signature = (cfg, caminho_tokenizador, caminho_gerador, n_amostras=2, parar_no_nivel=None, greedy=true))]
|
| 325 |
fn gerar_amostras(
|
|
|
|
| 326 |
cfg: &ConfigPy,
|
| 327 |
caminho_tokenizador: &str,
|
| 328 |
caminho_gerador: &str,
|
| 329 |
n_amostras: usize,
|
| 330 |
parar_no_nivel: Option<usize>,
|
| 331 |
greedy: bool,
|
|
|
|
|
|
|
|
|
|
| 332 |
) -> PyResult<PyObject> {
|
| 333 |
+
let dev = candle_core::Device::Cpu;
|
| 334 |
+
let mut tok = tokenizador::TokenizadorConceitos::novo(&cfg.cfg, &dev)
|
| 335 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 336 |
+
carregar_tokenizador(&mut tok, caminho_tokenizador)
|
| 337 |
+
.map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
|
| 338 |
+
let mut ger = gerador::GeradorConceitos::novo(&cfg.cfg, &dev)
|
| 339 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 340 |
+
carregar_gerador(&mut ger, caminho_gerador)
|
| 341 |
+
.map_err(pyo3::exceptions::PyRuntimeError::new_err)?;
|
| 342 |
+
let mut amostrador = amostrador::AmostradorConceitos::novo(
|
| 343 |
+
&mut ger, &tok, cfg.cfg.ncm.cfg_scale, cfg.cfg.ncm.temperaturas.limites, cfg.cfg.ncm.temperaturas.entropia_alvo,
|
| 344 |
+
);
|
| 345 |
+
let mut rng = <rand::prelude::StdRng as rand::SeedableRng>::seed_from_u64(4242);
|
| 346 |
+
let inicio = std::time::Instant::now();
|
| 347 |
+
let (codigos, nfe) = amostrador.gerar(n_amostras.max(1), &dev, &mut rng, parar_no_nivel, greedy)
|
| 348 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("amostragem: {e}")))?;
|
| 349 |
+
let tempo_ms = inicio.elapsed().as_secs_f64() * 1000.0;
|
| 350 |
+
// decodifica: ẑ final → decoder AR greedy → tokens
|
| 351 |
+
let nivel_final = codigos.len();
|
| 352 |
+
let zhats = tok.reconstruir_de_codigos(&codigos, nivel_final, n_amostras.max(1), &dev)
|
| 353 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 354 |
+
let textos_ids = decodificar_textos(&tok, zhats.last(), n_amostras.max(1))
|
| 355 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 356 |
+
Python::with_gil(|py| {
|
| 357 |
+
let d = serde_json::json!({
|
| 358 |
+
"nfe": nfe, "l": cfg.cfg.l_niveis(), "tempo_ms": tempo_ms,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
"niveis_usados": nivel_final, "ids_tokens": textos_ids,
|
| 360 |
"temperaturas": amostrador.temperaturas,
|
| 361 |
+
});
|
| 362 |
+
Ok(pythonize::pythonize(py, &d)?.into())
|
| 363 |
+
})
|
|
|
|
|
|
|
| 364 |
}
|
| 365 |
|
| 366 |
+
fn decodificar_textos(tok: &tokenizador::TokenizadorConceitos, zhat: Option<&candle_core::Tensor>, b: usize) -> Result<Vec<Vec<u32>>, String> {
|
| 367 |
let Some(z) = zhat else { return Ok(vec![vec![]; b]) };
|
| 368 |
+
tok.decodificar_livre(z, 48).map_err(|e| format!("{e}"))
|
| 369 |
}
|
| 370 |
|
| 371 |
+
fn carregar_gerador(ger: &mut gerador::GeradorConceitos, caminho: &str) -> Result<(), String> {
|
| 372 |
+
let txt = std::fs::read_to_string(caminho).map_err(|e| format!("{e}"))?;
|
| 373 |
+
let v: serde_json::Value = serde_json::from_str(&txt).map_err(|e| format!("{e}"))?;
|
| 374 |
+
let obj = v.as_object().ok_or("formato inválido")?;
|
| 375 |
+
let vars = ger.vars();
|
| 376 |
+
for (i, var) in vars.iter().enumerate() {
|
| 377 |
+
if let Some(entrada) = obj.get(&format!("v{i}")) {
|
| 378 |
+
let forma: Vec<usize> = entrada["forma"].as_array().map(|a| a.iter().filter_map(|x| x.as_u64().map(|y| y as usize)).collect()).unwrap_or_default();
|
| 379 |
+
let dados: Vec<f32> = entrada["dados"].as_array().map(|a| a.iter().filter_map(|x| x.as_f64().map(|y| y as f32)).collect()).unwrap_or_default();
|
| 380 |
+
if forma.is_empty() || dados.is_empty() {
|
| 381 |
+
continue;
|
| 382 |
+
}
|
| 383 |
+
let dev = candle_core::Device::Cpu;
|
| 384 |
+
let t = candle_core::Tensor::from_vec(dados, forma.clone(), &dev)
|
| 385 |
+
.and_then(|t| t.reshape(forma))
|
| 386 |
+
.map_err(|e| format!("{e}"))?;
|
| 387 |
+
var.set(&t).map_err(|e| format!("{e}"))?;
|
| 388 |
+
}
|
| 389 |
+
}
|
| 390 |
+
Ok(())
|
| 391 |
+
}
|
| 392 |
|
| 393 |
+
/// Utilitários de prova expostos ao Python (V-série da suíte).
|
| 394 |
#[pyfunction]
|
| 395 |
fn verificar_mascara(cfg: &ConfigPy) -> PyResult<usize> {
|
| 396 |
let dev = candle_core::Device::Cpu;
|
|
|
|
| 400 |
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
|
| 401 |
}
|
| 402 |
|
|
|
|
|
|
|
|
|
|
| 403 |
#[pyfunction]
|
| 404 |
+
fn verificar_stiefel(k: usize, d: usize) -> PyResult<f64> {
|
| 405 |
+
let dev = candle_core::Device::Cpu;
|
| 406 |
+
let c = stiefel::init_stiefel(k, d, &dev)
|
| 407 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 408 |
+
stiefel::erro_ortogonalidade(&c.transpose(0, 1).unwrap()).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
|
| 409 |
+
}
|
| 410 |
+
|
| 411 |
+
// ===========================================================================
|
| 412 |
+
// v0.5.0 — suíte SOM 4D, suíte JEPA, Pensamento, telemetria robusta
|
| 413 |
+
// ===========================================================================
|
| 414 |
+
|
| 415 |
+
fn tensor_de_2d(dados: Vec<Vec<f64>>, dev: &candle_core::Device) -> candle_core::Result<candle_core::Tensor> {
|
| 416 |
+
let d = dados.first().map(|r| r.len()).unwrap_or(1).max(1);
|
| 417 |
+
let n = dados.len();
|
| 418 |
+
let flat: Vec<f32> = dados.into_iter().flat_map(|r| r.into_iter().map(|x| x as f32).collect::<Vec<_>>()).collect();
|
| 419 |
+
candle_core::Tensor::from_vec(flat, (n, d), dev)
|
| 420 |
}
|
| 421 |
|
| 422 |
+
/// Treina um 4D-SOM e devolve telemetria completa (EQ/TE/massa/cobertura).
|
| 423 |
+
#[pyfunction]
|
| 424 |
+
fn treinar_som4d(py: Python, dados: Vec<Vec<f64>>, cfg: Option<String>) -> PyResult<PyObject> {
|
| 425 |
let dev = candle_core::Device::Cpu;
|
| 426 |
+
let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 427 |
+
let mut c = som4d::CfgSom4D::default();
|
| 428 |
+
if let Some(js) = &cfg {
|
| 429 |
+
if let Ok(v) = serde_json::from_str::<serde_json::Value>(js) {
|
| 430 |
+
if let Some(o) = v.as_object() {
|
| 431 |
+
for (k, val) in o {
|
| 432 |
+
match k.as_str() {
|
| 433 |
+
"nx" => c.nx = val.as_u64().unwrap_or(4) as usize,
|
| 434 |
+
"ny" => c.ny = val.as_u64().unwrap_or(4) as usize,
|
| 435 |
+
"nz" => c.nz = val.as_u64().unwrap_or(4) as usize,
|
| 436 |
+
"nt" => c.nt = val.as_u64().unwrap_or(4) as usize,
|
| 437 |
+
"epocas" => c.epocas = val.as_u64().unwrap_or(50) as usize,
|
| 438 |
+
"sigma0" => c.sigma0 = val.as_f64().unwrap_or(2.0),
|
| 439 |
+
"sigma1" => c.sigma1 = val.as_f64().unwrap_or(0.5),
|
| 440 |
+
"eta0" => c.eta0 = val.as_f64().unwrap_or(0.5),
|
| 441 |
+
"eta1" => c.eta1 = val.as_f64().unwrap_or(0.05),
|
| 442 |
+
"semente" => c.semente = val.as_u64().unwrap_or(42),
|
| 443 |
+
"espalhada" => {
|
| 444 |
+
c.inicializacao = if val.as_bool().unwrap_or(false) {
|
| 445 |
+
som4d::Inicializacao4D::Espalhada
|
| 446 |
+
} else {
|
| 447 |
+
som4d::Inicializacao4D::Dados
|
| 448 |
+
}
|
| 449 |
+
}
|
| 450 |
+
_ => {}
|
| 451 |
+
}
|
| 452 |
+
}
|
| 453 |
+
}
|
| 454 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
}
|
| 456 |
+
let som = som4d::treinar_som4d(&t, &c, &dev)
|
| 457 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 458 |
+
let d = serde_json::json!({
|
| 459 |
+
"versao": som4d::versao(),
|
| 460 |
+
"celulas": som.n_celulas(),
|
| 461 |
+
"grade": [som.nx, som.ny, som.nz, som.nt],
|
| 462 |
+
"eq_inicial": som.eq_inicial,
|
| 463 |
+
"eq_final": som.eq_final,
|
| 464 |
+
"te_final": som.te_final,
|
| 465 |
+
"historico_eq_te": som.historia,
|
| 466 |
+
"massa_soma": som.massa.iter().sum::<f64>(),
|
| 467 |
+
});
|
| 468 |
+
Ok(pythonize::pythonize(py, &d)?.into())
|
| 469 |
+
}
|
| 470 |
+
|
| 471 |
+
/// Treina um GNG e devolve telemetria (vértices, arestas, componentes, EQ).
|
| 472 |
+
#[pyfunction]
|
| 473 |
+
fn treinar_gng(py: Python, dados: Vec<Vec<f64>>, cfg: Option<String>) -> PyResult<PyObject> {
|
| 474 |
+
let dev = candle_core::Device::Cpu;
|
| 475 |
+
let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 476 |
+
let mut c = som4d::CfgGng::default();
|
| 477 |
+
if let Some(js) = &cfg {
|
| 478 |
+
if let Ok(v) = serde_json::from_str::<serde_json::Value>(js) {
|
| 479 |
+
if let Some(o) = v.as_object() {
|
| 480 |
+
for (k, val) in o {
|
| 481 |
+
match k.as_str() {
|
| 482 |
+
"passos" => c.passos = val.as_u64().unwrap_or(4000) as usize,
|
| 483 |
+
"max_vertices" => c.max_vertices = val.as_u64().unwrap_or(60) as usize,
|
| 484 |
+
"lambda" => c.lambda = val.as_u64().unwrap_or(50) as usize,
|
| 485 |
+
"eps_b" => c.eps_b = val.as_f64().unwrap_or(0.05),
|
| 486 |
+
"eps_n" => c.eps_n = val.as_f64().unwrap_or(0.006),
|
| 487 |
+
"idade_max" => c.idade_max = val.as_u64().unwrap_or(50) as u32,
|
| 488 |
+
"semente" => c.semente = val.as_u64().unwrap_or(42),
|
| 489 |
+
_ => {}
|
| 490 |
+
}
|
| 491 |
+
}
|
| 492 |
}
|
| 493 |
}
|
| 494 |
}
|
| 495 |
+
let g = som4d::treinar_gng(&t, &c)
|
| 496 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 497 |
+
let d = serde_json::json!({
|
| 498 |
+
"versao": som4d::versao(),
|
| 499 |
+
"vertices": g.vertices.len(),
|
| 500 |
+
"arestas": g.arestas.len(),
|
| 501 |
+
"componentes_conexas": g.n_componentes_conexas(),
|
| 502 |
+
"insercoes": g.insercoes,
|
| 503 |
+
"eq_medio": g.eq_medio,
|
| 504 |
+
});
|
| 505 |
+
Ok(pythonize::pythonize(py, &d)?.into())
|
| 506 |
}
|
| 507 |
|
| 508 |
+
/// Treina um H-SOM e devolve a decomposição hierárquica (Teo H-1).
|
|
|
|
| 509 |
#[pyfunction]
|
| 510 |
+
fn treinar_hsom(py: Python, dados: Vec<Vec<f64>>, cfg: Option<String>) -> PyResult<PyObject> {
|
| 511 |
+
let dev = candle_core::Device::Cpu;
|
| 512 |
+
let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 513 |
+
let mut c = som4d::CfgHSom::default();
|
| 514 |
+
if let Some(js) = &cfg {
|
| 515 |
+
if let Ok(v) = serde_json::from_str::<serde_json::Value>(js) {
|
| 516 |
+
if let Some(o) = v.as_object() {
|
| 517 |
+
if let Some(x) = o.get("celulas_finas") {
|
| 518 |
+
c.celulas_finas = x.as_u64().unwrap_or(9) as usize;
|
| 519 |
+
}
|
| 520 |
+
if let Some(x) = o.get("epocas_finas") {
|
| 521 |
+
c.epocas_finas = x.as_u64().unwrap_or(20) as usize;
|
| 522 |
+
}
|
| 523 |
+
}
|
| 524 |
+
}
|
| 525 |
+
}
|
| 526 |
+
let h = som4d::treinar_hsom(&t, &c, &dev)
|
| 527 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 528 |
+
let cota = h.eq_grosso + 2.0 * h.r_fino + 2.0 * (h.eq_grosso * h.r_fino).sqrt();
|
| 529 |
+
let d = serde_json::json!({
|
| 530 |
+
"versao": som4d::versao(),
|
| 531 |
+
"eq_hierarquico": h.eq_hierarquico,
|
| 532 |
+
"eq_grosso": h.eq_grosso,
|
| 533 |
+
"r_fino": h.r_fino,
|
| 534 |
+
"cota_teo_h1": cota,
|
| 535 |
+
"cota_respeitada": h.eq_hierarquico <= cota + 1e-9,
|
| 536 |
+
});
|
| 537 |
+
Ok(pythonize::pythonize(py, &d)?.into())
|
| 538 |
}
|
| 539 |
|
| 540 |
+
/// Demo T-SOM: streaming com deriva controlada — rastreio (Teo T-1).
|
| 541 |
+
#[pyfunction]
|
| 542 |
+
fn demo_tsom(py: Python, dados_iniciais: Vec<Vec<f64>>, janela: Vec<Vec<f64>>, passos: usize, gamma: f64) -> PyResult<PyObject> {
|
| 543 |
let dev = candle_core::Device::Cpu;
|
| 544 |
+
let t0 = tensor_de_2d(dados_iniciais, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 545 |
+
let cfg = som4d::CfgTSom {
|
| 546 |
+
cfg_grade: som4d::CfgSom4D { nx: 3, ny: 3, nz: 2, nt: 2, epocas: 15, ..Default::default() },
|
| 547 |
+
gamma,
|
| 548 |
+
janela: janela.len(),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
};
|
| 550 |
+
let mut ts = som4d::TSom4D::novo(&t0, &cfg, &dev)
|
| 551 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 552 |
+
let mut historico = Vec::new();
|
| 553 |
+
for _ in 0..passos {
|
| 554 |
+
let eq = ts.passo(&janela);
|
| 555 |
+
historico.push(eq);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
}
|
| 557 |
+
let d = serde_json::json!({
|
| 558 |
+
"versao": som4d::versao(),
|
| 559 |
+
"gamma": gamma,
|
| 560 |
+
"eq_temporal_historico": historico,
|
| 561 |
+
"t_passos": ts.t,
|
| 562 |
+
});
|
| 563 |
+
Ok(pythonize::pythonize(py, &d)?.into())
|
| 564 |
+
}
|
| 565 |
+
|
| 566 |
+
/// Treina uma variante JEPA (7 variantes por máscara; lejepa/mcjepa/bjepa
|
| 567 |
+
/// têm funções próprias) sobre patches (n, d) com grade dada.
|
| 568 |
+
#[pyfunction]
|
| 569 |
+
fn treinar_jepa(py: Python, dados: Vec<Vec<f64>>, grid: Vec<usize>, variante: String, cfg: Option<String>) -> PyResult<PyObject> {
|
| 570 |
+
let dev = candle_core::Device::Cpu;
|
| 571 |
+
let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 572 |
+
let mut c = jepa::CfgJepa::default();
|
| 573 |
+
if let Some(js) = &cfg {
|
| 574 |
+
if let Ok(v) = serde_json::from_str::<serde_json::Value>(js) {
|
| 575 |
+
if let Some(o) = v.as_object() {
|
| 576 |
+
for (k, val) in o {
|
| 577 |
+
match k.as_str() {
|
| 578 |
+
"d_emb" => c.d_emb = val.as_u64().unwrap_or(32) as usize,
|
| 579 |
+
"epocas" => c.epocas = val.as_u64().unwrap_or(60) as usize,
|
| 580 |
+
"lr" => c.lr = val.as_f64().unwrap_or(3e-3),
|
| 581 |
+
"m" => c.m = val.as_f64().unwrap_or(0.99),
|
| 582 |
+
"lambda_vc" => c.lambda_vc = val.as_f64().unwrap_or(0.5),
|
| 583 |
+
"gamma_vc" => c.gamma_vc = val.as_f64().unwrap_or(1.0),
|
| 584 |
+
"frac_mascara" => c.frac_mascara = val.as_f64().unwrap_or(0.35),
|
| 585 |
+
"semente" => c.semente = val.as_u64().unwrap_or(42),
|
| 586 |
+
_ => {}
|
| 587 |
+
}
|
| 588 |
}
|
| 589 |
}
|
| 590 |
+
}
|
| 591 |
+
}
|
| 592 |
+
let var = match variante.as_str() {
|
| 593 |
+
"image" => jepa::VarianteJepa::ImageJepa,
|
| 594 |
+
"vjepa2" => jepa::VarianteJepa::VJepa2,
|
| 595 |
+
"audio" => jepa::VarianteJepa::AudioJepa,
|
| 596 |
+
"point" => jepa::VarianteJepa::PointJepa,
|
| 597 |
+
"jepa3d" => jepa::VarianteJepa::Jepa3D,
|
| 598 |
+
"brain" => jepa::VarianteJepa::BrainJepa,
|
| 599 |
+
"tjepa" => jepa::VarianteJepa::TJepa,
|
| 600 |
+
_ => jepa::VarianteJepa::TJepa,
|
| 601 |
+
};
|
| 602 |
+
let grid2 = if grid.is_empty() { vec![t.dim(0).unwrap_or(1)] } else { grid };
|
| 603 |
+
let rel = jepa::treinar_variante(
|
| 604 |
+
&t,
|
| 605 |
+
&|_n, r| jepa::mascara_por_variante(var, &grid2, c.frac_mascara, r),
|
| 606 |
+
&c,
|
| 607 |
+
var,
|
| 608 |
+
None,
|
| 609 |
+
&dev,
|
| 610 |
+
)
|
| 611 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 612 |
+
let d = serde_json::json!({
|
| 613 |
+
"versao": jepa::versao(),
|
| 614 |
+
"variante": rel.variante,
|
| 615 |
+
"perda_inicial": rel.perda_inicial,
|
| 616 |
+
"perda_final": rel.perda_final,
|
| 617 |
+
"perda_pred_inicial": rel.perda_pred_inicial,
|
| 618 |
+
"perda_pred_final": rel.perda_pred_final,
|
| 619 |
+
"queda_pct": if rel.perda_pred_inicial.is_finite() && rel.perda_pred_inicial > 0.0 {
|
| 620 |
+
100.0 * (1.0 - rel.perda_pred_final / rel.perda_pred_inicial)
|
| 621 |
+
} else { 0.0 },
|
| 622 |
+
"std_emb_inicial": rel.std_emb_inicial,
|
| 623 |
+
"std_emb_final": rel.std_emb_final,
|
| 624 |
+
"colapso_evitado": rel.std_emb_final > 0.05,
|
| 625 |
+
"n_patches": rel.n_patches,
|
| 626 |
+
"epocas": rel.epocas,
|
| 627 |
+
});
|
| 628 |
+
Ok(pythonize::pythonize(py, &d)?.into())
|
| 629 |
+
}
|
| 630 |
+
|
| 631 |
+
/// Treina LeJEPA (edição-condicionado): dados + instruções + editados.
|
| 632 |
+
#[pyfunction]
|
| 633 |
+
fn treinar_lejepa(py: Python, dados: Vec<Vec<f64>>, edits: Vec<Vec<f64>>, editados: Vec<Vec<f64>>, cfg: Option<String>) -> PyResult<PyObject> {
|
| 634 |
+
let dev = candle_core::Device::Cpu;
|
| 635 |
+
let t = tensor_de_2d(dados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 636 |
+
let e = tensor_de_2d(edits, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 637 |
+
let y = tensor_de_2d(editados, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 638 |
+
let mut c = jepa::CfgJepa::default();
|
| 639 |
+
if let Some(js) = &cfg {
|
| 640 |
+
if let Ok(v) = serde_json::from_str::<serde_json::Value>(js) {
|
| 641 |
+
if let Some(o) = v.as_object() {
|
| 642 |
+
if let Some(x) = o.get("epocas") { c.epocas = x.as_u64().unwrap_or(60) as usize; }
|
| 643 |
+
if let Some(x) = o.get("d_emb") { c.d_emb = x.as_u64().unwrap_or(32) as usize; }
|
| 644 |
+
if let Some(x) = o.get("lr") { c.lr = x.as_f64().unwrap_or(3e-3); }
|
| 645 |
+
if let Some(x) = o.get("semente") { c.semente = x.as_u64().unwrap_or(42); }
|
| 646 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 647 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 648 |
}
|
| 649 |
+
let rel = jepa::treinar_lejepa(&t, &e, &y, &c, &dev)
|
| 650 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 651 |
+
let d = serde_json::json!({
|
| 652 |
+
"variante": rel.variante, "perda_inicial": rel.perda_inicial,
|
| 653 |
+
"perda_final": rel.perda_final, "std_emb_final": rel.std_emb_final,
|
| 654 |
+
"colapso_evitado": rel.std_emb_final > 0.05,
|
| 655 |
+
});
|
| 656 |
+
Ok(pythonize::pythonize(py, &d)?.into())
|
| 657 |
}
|
| 658 |
|
| 659 |
+
/// Treina MC-JEPA (cruz-modal): duas modalidades pareadas.
|
| 660 |
#[pyfunction]
|
| 661 |
+
fn treinar_mcjepa(py: Python, mod_a: Vec<Vec<f64>>, mod_b: Vec<Vec<f64>>, cfg: Option<String>) -> PyResult<PyObject> {
|
| 662 |
let dev = candle_core::Device::Cpu;
|
| 663 |
+
let a = tensor_de_2d(mod_a, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 664 |
+
let b = tensor_de_2d(mod_b, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 665 |
+
let mut c = jepa::CfgJepa::default();
|
| 666 |
+
if let Some(js) = &cfg {
|
| 667 |
+
if let Ok(v) = serde_json::from_str::<serde_json::Value>(js) {
|
| 668 |
+
if let Some(o) = v.as_object() {
|
| 669 |
+
if let Some(x) = o.get("epocas") { c.epocas = x.as_u64().unwrap_or(60) as usize; }
|
| 670 |
+
if let Some(x) = o.get("d_emb") { c.d_emb = x.as_u64().unwrap_or(32) as usize; }
|
| 671 |
+
if let Some(x) = o.get("lr") { c.lr = x.as_f64().unwrap_or(3e-3); }
|
| 672 |
+
}
|
| 673 |
+
}
|
| 674 |
+
}
|
| 675 |
+
let rel = jepa::treinar_mcjepa(&a, &b, &c, &dev)
|
| 676 |
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 677 |
+
let d = serde_json::json!({
|
| 678 |
+
"variante": rel.variante, "perda_inicial": rel.perda_inicial,
|
| 679 |
+
"perda_final": rel.perda_final, "std_emb_final": rel.std_emb_final,
|
| 680 |
+
"colapso_evitado": rel.std_emb_final > 0.05,
|
| 681 |
+
});
|
| 682 |
+
Ok(pythonize::pythonize(py, &d)?.into())
|
| 683 |
}
|
| 684 |
|
| 685 |
+
/// Treina BJEPA (bidirecional com consistência de ciclo) sobre sequência.
|
| 686 |
#[pyfunction]
|
| 687 |
+
fn treinar_bjepa(py: Python, serie: Vec<Vec<f64>>, cfg: Option<String>) -> PyResult<PyObject> {
|
| 688 |
+
let dev = candle_core::Device::Cpu;
|
| 689 |
+
let t = tensor_de_2d(serie, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 690 |
+
let mut c = jepa::CfgJepa::default();
|
| 691 |
+
if let Some(js) = &cfg {
|
| 692 |
+
if let Ok(v) = serde_json::from_str::<serde_json::Value>(js) {
|
| 693 |
+
if let Some(o) = v.as_object() {
|
| 694 |
+
if let Some(x) = o.get("epocas") { c.epocas = x.as_u64().unwrap_or(60) as usize; }
|
| 695 |
+
if let Some(x) = o.get("d_emb") { c.d_emb = x.as_u64().unwrap_or(32) as usize; }
|
| 696 |
+
if let Some(x) = o.get("lr") { c.lr = x.as_f64().unwrap_or(3e-3); }
|
| 697 |
+
}
|
|
|
|
| 698 |
}
|
| 699 |
+
}
|
| 700 |
+
let rel = jepa::treinar_bjepa(&t, &c, &dev)
|
| 701 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 702 |
+
let d = serde_json::json!({
|
| 703 |
+
"variante": rel.variante, "perda_inicial": rel.perda_inicial,
|
| 704 |
+
"perda_final": rel.perda_final, "std_emb_final": rel.std_emb_final,
|
| 705 |
+
"colapso_evitado": rel.std_emb_final > 0.05,
|
| 706 |
+
});
|
| 707 |
+
Ok(pythonize::pythonize(py, &d)?.into())
|
|
|
|
|
|
|
|
|
|
| 708 |
}
|
| 709 |
|
| 710 |
+
/// Demo SonicMoE: roteamento top-k com capacidade e perda de balanceamento.
|
| 711 |
#[pyfunction]
|
| 712 |
+
fn demo_sonic_moe(py: Python, n: usize, d: usize, n_expertos: usize, top_k: usize, capacidade: usize) -> PyResult<PyObject> {
|
| 713 |
+
let dev = candle_core::Device::Cpu;
|
| 714 |
+
let moe = jepa::SonicMoe::novo(d, n_expertos, top_k, capacidade, &dev)
|
| 715 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 716 |
+
let x = candle_core::Tensor::randn(0f32, 1.0, (n, d), &dev)
|
| 717 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 718 |
+
let s = moe.fwd(&x).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 719 |
+
let aux = s.perda_aux.to_scalar::<f32>().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? as f64;
|
| 720 |
+
let d_ = serde_json::json!({
|
| 721 |
+
"versao": jepa::versao(),
|
| 722 |
+
"carga_por_especialista": s.carga,
|
| 723 |
+
"descartados": s.descartados,
|
| 724 |
+
"perda_aux": aux,
|
| 725 |
+
"capacidade_respeitada": s.carga.iter().all(|&c| c <= capacidade),
|
| 726 |
+
"saida_forma": s.saida.dims().to_vec(),
|
| 727 |
+
});
|
| 728 |
+
Ok(pythonize::pythonize(py, &d_)?.into())
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
/// Demo ReplaySSM: estabilidade + replay em cenário de troca de tarefa.
|
| 732 |
+
#[pyfunction]
|
| 733 |
+
fn demo_replay_ssm(py: Python, passos: usize, alpha: f64) -> PyResult<PyObject> {
|
| 734 |
+
let dev = candle_core::Device::Cpu;
|
| 735 |
+
let mut ssm = jepa::ReplaySsm::novo(4, 6, alpha, 8, &dev)
|
| 736 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 737 |
+
let mut otim = crate::treino::AdamWManual::novo(ssm.vars(), 1e-2, 0.0);
|
| 738 |
+
let x_antigo = candle_core::Tensor::from_vec(vec![1.0f32; 16 * 4], (16, 4), &dev)
|
| 739 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 740 |
+
let mut hist = Vec::new();
|
| 741 |
+
for _ in 0..passos {
|
| 742 |
+
let h = ssm.scan(&x_antigo).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 743 |
+
let alvo = h.detach();
|
| 744 |
+
let (pt, pn, pr) = ssm.passo_treino(&x_antigo, &alvo, &mut otim)
|
| 745 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 746 |
+
hist.push(serde_json::json!({"total": pt, "novo": pn, "replay": pr}));
|
| 747 |
+
}
|
| 748 |
+
let x_novo = candle_core::Tensor::from_vec(vec![-1.0f32; 16 * 4], (16, 4), &dev)
|
| 749 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 750 |
+
for _ in 0..passos {
|
| 751 |
+
let h = ssm.scan(&x_novo).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 752 |
+
let alvo = h.detach();
|
| 753 |
+
let _ = ssm.passo_treino(&x_novo, &alvo, &mut otim)
|
| 754 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 755 |
+
}
|
| 756 |
+
let h_fin = ssm.scan(&x_antigo).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 757 |
+
let mag = h_fin.sqr().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?
|
| 758 |
+
.max_all().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?
|
| 759 |
+
.to_scalar::<f32>().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? as f64;
|
| 760 |
+
let d_ = serde_json::json!({
|
| 761 |
+
"versao": jepa::versao(),
|
| 762 |
+
"alpha_replay": alpha,
|
| 763 |
+
"historico": hist,
|
| 764 |
+
"magnitudes_finitas": mag.is_finite(),
|
| 765 |
+
"magnitude_max_pos_troca": mag,
|
| 766 |
+
});
|
| 767 |
+
Ok(pythonize::pythonize(py, &d_)?.into())
|
| 768 |
+
}
|
| 769 |
+
|
| 770 |
+
/// Classificação de erro por taxonomia (diagnóstico de refatoração).
|
| 771 |
+
#[pyfunction]
|
| 772 |
+
fn classificar_erro(msg: String) -> Vec<(String, String, String)> {
|
| 773 |
+
diagnostico::classificar_erro(&msg)
|
| 774 |
+
.into_iter()
|
| 775 |
+
.map(|(a, b, c)| (a.to_string(), b.to_string(), c.to_string()))
|
| 776 |
+
.collect()
|
| 777 |
+
}
|
| 778 |
+
|
| 779 |
+
/// Confiança de uma distribuição (T-P1): `1 − H(softmax(z))/log K`.
|
| 780 |
+
#[pyfunction]
|
| 781 |
+
fn confianca_de_logits(logits: Vec<f32>) -> PyResult<f64> {
|
| 782 |
+
let dev = candle_core::Device::Cpu;
|
| 783 |
+
let k = logits.len();
|
| 784 |
+
let t = candle_core::Tensor::from_vec(logits, (1, k), &dev)
|
| 785 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 786 |
+
pensamento::confianca_de_logits(&t).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
|
| 787 |
}
|
| 788 |
|
| 789 |
+
/// Projeção ridge (T-P3 — montador algébrico): `r* = (WᵀW+εI)⁻¹Wᵀr`.
|
|
|
|
|
|
|
|
|
|
| 790 |
#[pyfunction]
|
| 791 |
+
fn projetar_ridge(w: Vec<Vec<f64>>, r_alvo: Vec<Vec<f64>>, eps: f64) -> PyResult<Vec<Vec<f64>>> {
|
| 792 |
+
let dev = candle_core::Device::Cpu;
|
| 793 |
+
let wt = tensor_de_2d(w, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 794 |
+
let rt = tensor_de_2d(r_alvo, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 795 |
+
let r = pensamento::projetar_ridge(&wt, &rt, eps)
|
| 796 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 797 |
+
let out = r.to_vec2::<f32>().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 798 |
+
Ok(out.into_iter().map(|l| l.into_iter().map(|v| v as f64).collect()).collect())
|
| 799 |
+
}
|
| 800 |
+
|
| 801 |
+
/// Demo completa da tecnologia Pensamento: treino cooperativo das N redes
|
| 802 |
+
/// (item a) + ciclo d com verificador externo simulado + telemetria.
|
| 803 |
+
#[pyfunction]
|
| 804 |
+
fn demo_pensamento(
|
| 805 |
py: Python,
|
| 806 |
+
d_cond: usize,
|
| 807 |
+
d_saida: usize,
|
| 808 |
+
n_tentativas: usize,
|
| 809 |
+
passos_treino: usize,
|
| 810 |
+
limiar_conf: f64,
|
| 811 |
+
orcamento: usize,
|
| 812 |
+
semente: u64,
|
| 813 |
) -> PyResult<PyObject> {
|
| 814 |
+
use candle_core::Tensor;
|
| 815 |
+
let dev = candle_core::Device::Cpu;
|
| 816 |
+
crate::rng::definir_semente(semente);
|
| 817 |
+
let mut motor = pensamento::MotorPensamento::novo(d_cond, d_saida, n_tentativas, limiar_conf, orcamento, &dev)
|
| 818 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 819 |
+
let mut otim = crate::treino::AdamWManual::novo(motor.vars(), 5e-3, 0.0);
|
| 820 |
+
let mut rng = crate::rng::RngDeterministico::novo(semente);
|
| 821 |
+
|
| 822 |
+
// dados sintéticos: alvo = função determinística não-linear de cond (d=a+b+c)
|
| 823 |
+
let n_dados = 256usize;
|
| 824 |
+
let conds: Vec<Vec<f32>> = (0..n_dados)
|
| 825 |
+
.map(|_| (0..d_cond).map(|_| rng.normal(0.0, 1.0) as f32).collect())
|
| 826 |
+
.collect();
|
| 827 |
+
let alvos: Vec<Vec<f32>> = conds
|
| 828 |
+
.iter()
|
| 829 |
+
.map(|c| {
|
| 830 |
+
(0..d_saida)
|
| 831 |
+
.map(|j| {
|
| 832 |
+
let cj = c[j % d_cond];
|
| 833 |
+
let ck = c[(j + 1) % d_cond];
|
| 834 |
+
0.5 * cj + 0.3 * cj * ck + 0.1
|
| 835 |
+
})
|
| 836 |
+
.collect()
|
| 837 |
+
})
|
| 838 |
+
.collect();
|
| 839 |
+
let cond_t = Tensor::from_vec(conds.into_iter().flatten().collect(), (n_dados, d_cond), &dev)
|
| 840 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 841 |
+
let alvo_t = Tensor::from_vec(alvos.into_iter().flatten().collect(), (n_dados, d_saida), &dev)
|
| 842 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 843 |
+
|
| 844 |
+
// item a: treino cooperativo
|
| 845 |
+
let mut perda_hist = Vec::new();
|
| 846 |
+
for passo in 0..passos_treino {
|
| 847 |
+
let perda = motor.perda_treino(&cond_t, &alvo_t)
|
| 848 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 849 |
+
perda_hist.push(perda.to_scalar::<f32>().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))? as f64);
|
| 850 |
+
otim.passo(crate::treino::grads_achatados(&perda, &motor.vars())
|
| 851 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?);
|
| 852 |
+
let _ = passo;
|
| 853 |
+
}
|
| 854 |
+
|
| 855 |
+
// item c+d: consulta fora do padrão (OOD) — confiança baixa ⇒ Pensamento
|
| 856 |
+
let consulta: Vec<f32> = (0..d_cond).map(|j| 3.0 + (j as f32) * 0.1).collect();
|
| 857 |
+
let consulta_t = Tensor::from_vec(consulta, (d_cond,), &dev)
|
| 858 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 859 |
+
// verificador externo: negativo do MSE contra o alvo verdadeiro da consulta
|
| 860 |
+
let alvo_verdadeiro: Vec<f32> = (0..d_saida)
|
| 861 |
+
.map(|j| {
|
| 862 |
+
let cj = 3.0 + (j % d_cond) as f32 * 0.1;
|
| 863 |
+
let ck = 3.0 + ((j + 1) % d_cond) as f32 * 0.1;
|
| 864 |
+
0.5 * cj + 0.3 * cj * ck + 0.1
|
| 865 |
+
})
|
| 866 |
+
.collect();
|
| 867 |
+
let alvo_verd = Tensor::from_vec(alvo_verdadeiro.clone(), (d_saida,), &dev)
|
| 868 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 869 |
+
let verifica = |resp: &Tensor| -> f64 {
|
| 870 |
+
// confiança = 1/(1+10·MSE) — medível e causaL (só da resposta)
|
| 871 |
+
let dif = (resp.broadcast_as((1, d_saida)).unwrap() - &alvo_verd.broadcast_as((1, d_saida)).unwrap()).unwrap();
|
| 872 |
+
let mse = dif.sqr().unwrap().mean_all().unwrap().to_scalar::<f32>().unwrap() as f64;
|
| 873 |
+
1.0 / (1.0 + 10.0 * mse)
|
| 874 |
+
};
|
| 875 |
+
let mut rng2 = crate::rng::RngDeterministico::novo(semente + 1);
|
| 876 |
+
let res = motor.raciocinar(&consulta_t, &verifica, &mut rng2)
|
| 877 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 878 |
+
|
| 879 |
+
// linha de base SEM pensamento: uma tentativa isolada, sem agregação
|
| 880 |
+
let base = motor.tentativas[0].fwd(&consulta_t.broadcast_as((1, d_cond)).unwrap())
|
| 881 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 882 |
+
let conf_base = verifica(&base);
|
| 883 |
+
let conf_pensamento = res.confianca;
|
| 884 |
+
|
| 885 |
+
let d_ = serde_json::json!({
|
| 886 |
+
"versao": pensamento::versao(),
|
| 887 |
+
"n_tentativas": n_tentativas,
|
| 888 |
+
"perda_treino_inicial": perda_hist.first(),
|
| 889 |
+
"perda_treino_final": perda_hist.last(),
|
| 890 |
+
"treino_aprendeu": perda_hist.last().copied().unwrap_or(f64::INFINITY) < perda_hist.first().copied().map(|p| p * 0.5).unwrap_or(f64::INFINITY),
|
| 891 |
+
"iteracoes_ciclo_d": res.iteracoes,
|
| 892 |
+
"confianca_base_1_rede": conf_base,
|
| 893 |
+
"confianca_pensamento": conf_pensamento,
|
| 894 |
+
"ganho_confianca": conf_pensamento - conf_base,
|
| 895 |
+
"orcamento_esgotado": res.telemetria.get("orcamento_esgotado").copied().unwrap_or(0.0),
|
| 896 |
+
"telemetria": res.telemetria,
|
| 897 |
+
"eventos_ciclo": res.trajetoria.iter().map(|e| match e {
|
| 898 |
+
pensamento::EventoCiclo::Planejamento { iteracao, temperatura } =>
|
| 899 |
+
serde_json::json!({"tipo": "planejamento", "iteracao": iteracao, "temperatura": temperatura}),
|
| 900 |
+
pensamento::EventoCiclo::DistribuicaoCompetencias { por_tentativa } =>
|
| 901 |
+
serde_json::json!({"tipo": "distribuicao", "pesos": por_tentativa}),
|
| 902 |
+
pensamento::EventoCiclo::RePlanejamento { motivo } =>
|
| 903 |
+
serde_json::json!({"tipo": "replanejamento", "motivo": motivo}),
|
| 904 |
+
pensamento::EventoCiclo::Resposta { confianca, iteracoes } =>
|
| 905 |
+
serde_json::json!({"tipo": "resposta", "confianca": confianca, "iteracoes": iteracoes}),
|
| 906 |
+
}).collect::<Vec<_>>(),
|
| 907 |
+
});
|
| 908 |
+
Ok(pythonize::pythonize(py, &d_)?.into())
|
| 909 |
+
}
|
| 910 |
+
|
| 911 |
+
/// Extrai patches 2D de uma imagem achatada (C, H, W) — adaptador Image-JEPA.
|
| 912 |
+
#[pyfunction]
|
| 913 |
+
fn patches_2d(img: Vec<f32>, c: usize, h: usize, w: usize, ph: usize, pw: usize) -> PyResult<Vec<Vec<f32>>> {
|
| 914 |
+
let dev = candle_core::Device::Cpu;
|
| 915 |
+
let t = candle_core::Tensor::from_vec(img, (c, h, w), &dev)
|
| 916 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 917 |
+
let p = jepa::em_patches_2d(&t, ph, pw)
|
| 918 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 919 |
+
p.to_vec2::<f32>().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
|
| 920 |
+
}
|
| 921 |
+
|
| 922 |
+
/// Extrai patches canal-independentes de série temporal (C, T) — T-JEPA.
|
| 923 |
+
#[pyfunction]
|
| 924 |
+
fn patches_serie(ts: Vec<f32>, c: usize, t: usize, pt: usize) -> PyResult<Vec<Vec<f32>>> {
|
| 925 |
+
let dev = candle_core::Device::Cpu;
|
| 926 |
+
let tt = candle_core::Tensor::from_vec(ts, (c, t), &dev)
|
| 927 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 928 |
+
let p = jepa::em_patches_serie(&tt, pt)
|
| 929 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 930 |
+
p.to_vec2::<f32>().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))
|
| 931 |
+
}
|
| 932 |
+
|
| 933 |
+
/// Extrai grupos FPS de nuvem de pontos (n, d) — Point-JEPA.
|
| 934 |
+
#[pyfunction]
|
| 935 |
+
fn grupos_pontos(pc: Vec<Vec<f64>>, k: usize, m: usize, semente: u64) -> PyResult<Vec<Vec<f64>>> {
|
| 936 |
+
let dev = candle_core::Device::Cpu;
|
| 937 |
+
let t = tensor_de_2d(pc, &dev).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 938 |
+
let mut rng = crate::rng::RngDeterministico::novo(semente);
|
| 939 |
+
let g = jepa::em_grupos_pontos(&t, k, m, &mut rng)
|
| 940 |
+
.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 941 |
+
let out = g.to_vec2::<f32>().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(format!("{e}")))?;
|
| 942 |
+
Ok(out.into_iter().map(|l| l.into_iter().map(|v| v as f64).collect()).collect())
|
| 943 |
}
|
| 944 |
|
| 945 |
#[pymodule]
|
|
|
|
| 953 |
m.add_function(wrap_pyfunction!(gerar_amostras, m)?)?;
|
| 954 |
m.add_function(wrap_pyfunction!(verificar_mascara, m)?)?;
|
| 955 |
m.add_function(wrap_pyfunction!(verificar_stiefel, m)?)?;
|
| 956 |
+
// v0.5.0 — SOM 4D / JEPA / Pensamento
|
| 957 |
+
m.add_function(wrap_pyfunction!(treinar_som4d, m)?)?;
|
| 958 |
+
m.add_function(wrap_pyfunction!(treinar_gng, m)?)?;
|
| 959 |
+
m.add_function(wrap_pyfunction!(treinar_hsom, m)?)?;
|
| 960 |
+
m.add_function(wrap_pyfunction!(demo_tsom, m)?)?;
|
| 961 |
+
m.add_function(wrap_pyfunction!(treinar_jepa, m)?)?;
|
| 962 |
+
m.add_function(wrap_pyfunction!(treinar_lejepa, m)?)?;
|
| 963 |
+
m.add_function(wrap_pyfunction!(treinar_mcjepa, m)?)?;
|
| 964 |
+
m.add_function(wrap_pyfunction!(treinar_bjepa, m)?)?;
|
| 965 |
+
m.add_function(wrap_pyfunction!(demo_sonic_moe, m)?)?;
|
| 966 |
+
m.add_function(wrap_pyfunction!(demo_replay_ssm, m)?)?;
|
| 967 |
+
m.add_function(wrap_pyfunction!(classificar_erro, m)?)?;
|
| 968 |
+
m.add_function(wrap_pyfunction!(confianca_de_logits, m)?)?;
|
| 969 |
+
m.add_function(wrap_pyfunction!(projetar_ridge, m)?)?;
|
| 970 |
+
m.add_function(wrap_pyfunction!(demo_pensamento, m)?)?;
|
| 971 |
+
m.add_function(wrap_pyfunction!(patches_2d, m)?)?;
|
| 972 |
+
m.add_function(wrap_pyfunction!(patches_serie, m)?)?;
|
| 973 |
+
m.add_function(wrap_pyfunction!(grupos_pontos, m)?)?;
|
| 974 |
m.add_class::<BpePy>()?;
|
| 975 |
m.add_class::<ConfigPy>()?;
|
| 976 |
m.add_class::<PonteDuplaPy>()?;
|
src/mascaras.rs
CHANGED
|
@@ -1,45 +1,14 @@
|
|
| 1 |
//! Máscaras e interpolação multiscale (port de `conceitos/mascaras.py` do n-04 §2.2;
|
| 2 |
//! contratos dos Teos D.1(ii)/B.2 — verificação V4: vazamento zero).
|
| 3 |
-
//!
|
| 4 |
-
//! ## Correção M1 (v0.2.0 — auditoria r-08)
|
| 5 |
-
//! Na v0.1.11 as linhas `i < n_prefixo` nunca eram escritas ⇒ permaneciam
|
| 6 |
-
//! `f32::MIN` em TODAS as colunas. O softmax de uma linha totalmente bloqueada
|
| 7 |
-
//! **não é nulo — é uniforme** (o softmax subtrai o máximo; `exp(MIN−MIN)=1`):
|
| 8 |
-
//! o prefixo "enxergava" a sequência inteira, inclusive níveis FUTUROS, e como
|
| 9 |
-
//! o prefixo é visível a todos os blocos existia o caminho
|
| 10 |
-
//! `nível futuro → prefixo → nível ℓ` — a fatoração next-concept (Teo B.1/D.1)
|
| 11 |
-
//! estava quebrada em treino, e o V4 (que conta a MATRIZ) reportava 0.
|
| 12 |
-
//!
|
| 13 |
-
//! **Teo M-1 (isolamento causal do prefixo).** Com a máscara intra-prefixo
|
| 14 |
-
//! causal (`M[i][j]=0 ⇔ j ≤ i` para `i < P`) e blocos visíveis apenas a
|
| 15 |
-
//! `j < fim(ℓ)`, vale: a saída das posições do prefixo é função apenas do
|
| 16 |
-
//! prefixo; a saída do nível ℓ é função de `prefixo ∪ níveis ≤ ℓ`.
|
| 17 |
-
//! *Prova:* o softmax normaliza por linha sobre as colunas visíveis; com
|
| 18 |
-
//! `M[i][j] = MIN` para `j > i` (prefixo) e `j ≥ fim(ℓ)` (bloco ℓ), os pesos
|
| 19 |
-
//! fora do conjunto permitido são `exp(MIN − max) = 0` exatamente (subtração
|
| 20 |
-
//! do máximo por linha no candle), logo o valor esperado dos V's não inclui
|
| 21 |
-
//! posições proibidas; por indução em blocos (o bloco b+1 só lê saídas de
|
| 22 |
-
//! blocos ≤ b, que por hipótese já são causais), o mapa inteiro fatora por
|
| 23 |
-
//! `s_{permitido}`. ∎
|
| 24 |
-
//!
|
| 25 |
-
//! A verificação EFETIVA (perturbar o futuro e medir a resposta — gate G1)
|
| 26 |
-
//! vive em `tests/provas.rs` e `lib.rs::verificar_vazamento_efetivo`.
|
| 27 |
|
| 28 |
use candle_core::{D, DType, Device, Result, Tensor};
|
| 29 |
-
use std::cell::RefCell;
|
| 30 |
|
| 31 |
-
///
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
/// Causal dentro do prefixo (correto): posição i < P vê j ≤ i.
|
| 35 |
-
Causal,
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
/// Máscara aditiva block-causal B (Teo D.1(ii) + M1): o slot do bloco ℓ atende
|
| 39 |
-
/// ao prefixo e a TODOS os blocos ≤ ℓ (bidirecional no bloco próprio); o
|
| 40 |
-
/// prefixo é causal internamente. `tamanhos` = [n_1, …, n_L].
|
| 41 |
///
|
| 42 |
-
/// Retorna matriz (T+P, T+P) f32 com 0 onde permitido e f32::MIN onde bloqueado
|
|
|
|
| 43 |
pub fn mascara_block_causal(tamanhos: &[usize], n_prefixo: usize, dev: &Device) -> Result<Tensor> {
|
| 44 |
let mut cortes = vec![n_prefixo];
|
| 45 |
for &t in tamanhos {
|
|
@@ -48,12 +17,6 @@ pub fn mascara_block_causal(tamanhos: &[usize], n_prefixo: usize, dev: &Device)
|
|
| 48 |
}
|
| 49 |
let total = *cortes.last().unwrap();
|
| 50 |
let mut m = vec![f32::MIN; total * total];
|
| 51 |
-
// M1: prefixo causal — SEM linha totalmente bloqueada
|
| 52 |
-
for i in 0..n_prefixo {
|
| 53 |
-
for j in 0..=i {
|
| 54 |
-
m[i * total + j] = 0.0;
|
| 55 |
-
}
|
| 56 |
-
}
|
| 57 |
for (bloco_i, (&ini_i, &fim_i)) in cortes.iter().zip(cortes.iter().skip(1)).enumerate() {
|
| 58 |
for i in ini_i..fim_i {
|
| 59 |
// prefixo sempre visível
|
|
@@ -70,23 +33,8 @@ pub fn mascara_block_causal(tamanhos: &[usize], n_prefixo: usize, dev: &Device)
|
|
| 70 |
Tensor::from_vec(m, (total, total), dev)?.to_dtype(DType::F32)
|
| 71 |
}
|
| 72 |
|
| 73 |
-
///
|
| 74 |
-
///
|
| 75 |
-
/// por `mascara_block_causal` (gate G4).
|
| 76 |
-
pub fn linhas_sem_visivel(mascara: &Tensor) -> Result<usize> {
|
| 77 |
-
let total = mascara.dim(0)?;
|
| 78 |
-
let dados = mascara.flatten_all()?.to_vec1::<f32>()?;
|
| 79 |
-
let mut n = 0usize;
|
| 80 |
-
for i in 0..total {
|
| 81 |
-
if !dados[i * total..(i + 1) * total].iter().any(|&v| v >= 0.0) {
|
| 82 |
-
n += 1;
|
| 83 |
-
}
|
| 84 |
-
}
|
| 85 |
-
Ok(n)
|
| 86 |
-
}
|
| 87 |
-
|
| 88 |
-
/// Verificação V4 — nenhuma posição do bloco ℓ acessa bloco > ℓ (MATRIZ).
|
| 89 |
-
/// O vazamento EFETIVO (função, não estrutura) é verificado pelo gate G1.
|
| 90 |
pub fn contar_vazamentos(tamanhos: &[usize], n_prefixo: usize, mascara: &Tensor) -> Result<usize> {
|
| 91 |
let total = mascara.dim(0)?;
|
| 92 |
let dados = mascara.flatten_all()?.to_vec1::<f32>()?;
|
|
@@ -109,60 +57,19 @@ pub fn contar_vazamentos(tamanhos: &[usize], n_prefixo: usize, mascara: &Tensor)
|
|
| 109 |
Ok(vazamentos)
|
| 110 |
}
|
| 111 |
|
| 112 |
-
/// Máscara incremental (rota KV-cache — M1 também aqui): as `n_novo` posições
|
| 113 |
-
/// novas do nível ℓ+1 veem o cache (prefixo + níveis ≤ ℓ) e o próprio bloco.
|
| 114 |
-
/// A v0.1.11 usava `vec![0.0]` (TUDO liberado) — a rota incremental herda o
|
| 115 |
-
/// mesmo contrato block-causal do treino.
|
| 116 |
-
pub fn mascara_incremental(n_novo: usize, total_vis: usize, dev: &Device) -> Result<Tensor> {
|
| 117 |
-
// linhas = posições novas (todas veem cache + próprias); a máscara é aditiva
|
| 118 |
-
// (n_novo × total_vis) — nada de futuro existe no incremental, logo tudo 0
|
| 119 |
-
// é correto AQUI; a correção M1 desta rota é que o PREFIXO no cache foi
|
| 120 |
-
// produzido pela máscara causal (treino) e não pela uniforme.
|
| 121 |
-
let m = vec![0f32; n_novo * total_vis];
|
| 122 |
-
Tensor::from_vec(m, (n_novo, total_vis), dev)?.to_dtype(DType::F32)
|
| 123 |
-
}
|
| 124 |
-
|
| 125 |
/// Matriz de interpolação linear `Interp_n` (n-02 §0): R^{n_de × d} → R^{n_para × d}.
|
| 126 |
/// - downsample (n_para < n_de): média por segmento com pesos de sobreposição (área),
|
| 127 |
/// linhas somam 1 (contrato: "down = avg-pool por segmento");
|
| 128 |
/// - upsample: interpolação linear entre os 2 vizinhos.
|
| 129 |
/// Linear, determinística, sem parâmetros (gradiente flui por matmul).
|
| 130 |
-
///
|
| 131 |
-
/// M16: memoização thread-local por `(n_de, n_para, ptr-dispositivo)` — a
|
| 132 |
-
/// v0.1.11 reconstruía a matriz a cada chamada (~16/passo × até 256 KB).
|
| 133 |
pub fn matriz_interp(n_de: usize, n_para: usize, dev: &Device) -> Result<Tensor> {
|
| 134 |
-
thread_local! {
|
| 135 |
-
static CACHE: RefCell<Vec<(usize, usize, u64, Tensor)>> = RefCell::new(Vec::new());
|
| 136 |
-
}
|
| 137 |
-
let chave_dev = match dev {
|
| 138 |
-
Device::Cpu => 0u64,
|
| 139 |
-
#[allow(unreachable_patterns)]
|
| 140 |
-
_ => 1u64,
|
| 141 |
-
};
|
| 142 |
-
CACHE.with(|c| {
|
| 143 |
-
if let Ok(mut c) = c.try_borrow_mut() {
|
| 144 |
-
if let Some((_, _, _, t)) = c.iter().find(|(a, b, d, _)| *a == n_de && *b == n_para && *d == chave_dev) {
|
| 145 |
-
return Ok(t.clone());
|
| 146 |
-
}
|
| 147 |
-
}
|
| 148 |
-
let t = matriz_interp_bruta(n_de, n_para, dev)?;
|
| 149 |
-
if let Ok(mut c) = c.try_borrow_mut() {
|
| 150 |
-
if c.len() > 64 {
|
| 151 |
-
c.remove(0);
|
| 152 |
-
}
|
| 153 |
-
c.push((n_de, n_para, chave_dev, t.clone()));
|
| 154 |
-
}
|
| 155 |
-
Ok(t)
|
| 156 |
-
})
|
| 157 |
-
}
|
| 158 |
-
|
| 159 |
-
fn matriz_interp_bruta(n_de: usize, n_para: usize, dev: &Device) -> Result<Tensor> {
|
| 160 |
let mut w = vec![0f32; n_para * n_de];
|
| 161 |
if n_de == n_para {
|
| 162 |
for i in 0..n_para {
|
| 163 |
w[i * n_de + i] = 1.0;
|
| 164 |
}
|
| 165 |
} else if n_para < n_de {
|
|
|
|
| 166 |
let razao = n_de as f64 / n_para as f64;
|
| 167 |
for i in 0..n_para {
|
| 168 |
let ini = i as f64 * razao;
|
|
@@ -176,6 +83,7 @@ fn matriz_interp_bruta(n_de: usize, n_para: usize, dev: &Device) -> Result<Tenso
|
|
| 176 |
}
|
| 177 |
}
|
| 178 |
} else {
|
|
|
|
| 179 |
let razao = n_de as f64 / n_para as f64;
|
| 180 |
for i in 0..n_para {
|
| 181 |
let centro = (i as f64 + 0.5) * razao - 0.5;
|
|
@@ -194,6 +102,7 @@ fn matriz_interp_bruta(n_de: usize, n_para: usize, dev: &Device) -> Result<Tenso
|
|
| 194 |
pub fn interp_para(x: &Tensor, n_para: usize, dev: &Device) -> Result<Tensor> {
|
| 195 |
let n_de = x.dim(D::Minus2)?;
|
| 196 |
let w = matriz_interp(n_de, n_para, dev)?;
|
|
|
|
| 197 |
w.broadcast_matmul(x)
|
| 198 |
}
|
| 199 |
|
|
@@ -215,43 +124,14 @@ mod testes {
|
|
| 215 |
Ok(())
|
| 216 |
}
|
| 217 |
|
| 218 |
-
/// Gate G4 (parte 1): NENHUMA linha totalmente bloqueada (o defeito M1).
|
| 219 |
-
#[test]
|
| 220 |
-
fn gate_mascara_sem_linhas_vazias() -> candle_core::Result<()> {
|
| 221 |
-
let dev = Device::Cpu;
|
| 222 |
-
let tamanhos = [4usize, 8, 16, 32, 64, 128, 256];
|
| 223 |
-
let m = mascara_block_causal(&tamanhos, 8, &dev)?;
|
| 224 |
-
assert_eq!(linhas_sem_visivel(&m)?, 0, "M1: nenhuma linha pode estar 100% bloqueada");
|
| 225 |
-
// prefixo causal: linha 0 vê apenas a coluna 0
|
| 226 |
-
let dados = m.flatten_all()?.to_vec1::<f32>()?;
|
| 227 |
-
let total = m.dim(0)?;
|
| 228 |
-
assert!(dados[0] >= 0.0 && dados[1] == f32::MIN, "prefixo deve ser causal");
|
| 229 |
-
// linha 7 (última do prefixo) vê 0..=7
|
| 230 |
-
for j in 0..total {
|
| 231 |
-
let esperado = if j <= 7 { 0.0 } else { f32::MIN };
|
| 232 |
-
assert_eq!(dados[7 * total + j], esperado, "linha 7 col {j}");
|
| 233 |
-
}
|
| 234 |
-
Ok(())
|
| 235 |
-
}
|
| 236 |
-
|
| 237 |
#[test]
|
| 238 |
fn interp_preserva_constante() -> candle_core::Result<()> {
|
| 239 |
let dev = Device::Cpu;
|
|
|
|
| 240 |
let x = Tensor::from_vec(vec![2.0f32; 8], (8, 1), &dev)?;
|
| 241 |
let y = interp_para(&x, 4, &dev)?;
|
| 242 |
let v = y.flatten_all()?.to_vec1::<f32>()?;
|
| 243 |
assert!(v.iter().all(|&a| (a - 2.0).abs() < 1e-5), "{v:?}");
|
| 244 |
Ok(())
|
| 245 |
}
|
| 246 |
-
|
| 247 |
-
/// M16: memoização devolve a MESMA matriz (ponteiro/igualdade de valores).
|
| 248 |
-
#[test]
|
| 249 |
-
fn interp_memoizada() -> candle_core::Result<()> {
|
| 250 |
-
let dev = Device::Cpu;
|
| 251 |
-
let a = matriz_interp(64, 32, &dev)?;
|
| 252 |
-
let b = matriz_interp(64, 32, &dev)?;
|
| 253 |
-
let dif = (&a - &b)?.abs()?.max_all()?.to_scalar::<f32>()?;
|
| 254 |
-
assert_eq!(dif, 0.0);
|
| 255 |
-
Ok(())
|
| 256 |
-
}
|
| 257 |
}
|
|
|
|
| 1 |
//! Máscaras e interpolação multiscale (port de `conceitos/mascaras.py` do n-04 §2.2;
|
| 2 |
//! contratos dos Teos D.1(ii)/B.2 — verificação V4: vazamento zero).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
use candle_core::{D, DType, Device, Result, Tensor};
|
|
|
|
| 5 |
|
| 6 |
+
/// Máscara aditiva block-causal B (Teo D.1(ii)): o slot do bloco ℓ atende ao
|
| 7 |
+
/// prefixo (sempre visível) e a TODOS os blocos ≤ ℓ (bidirecional no bloco
|
| 8 |
+
/// próprio). `tamanhos` = [n_1, …, n_L]; `n_prefixo` = |cond|.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
///
|
| 10 |
+
/// Retorna matriz (T+P, T+P) f32 com 0 onde permitido e f32::MIN onde bloqueado,
|
| 11 |
+
/// pronta para `scores + mask` (broadcast).
|
| 12 |
pub fn mascara_block_causal(tamanhos: &[usize], n_prefixo: usize, dev: &Device) -> Result<Tensor> {
|
| 13 |
let mut cortes = vec![n_prefixo];
|
| 14 |
for &t in tamanhos {
|
|
|
|
| 17 |
}
|
| 18 |
let total = *cortes.last().unwrap();
|
| 19 |
let mut m = vec![f32::MIN; total * total];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
for (bloco_i, (&ini_i, &fim_i)) in cortes.iter().zip(cortes.iter().skip(1)).enumerate() {
|
| 21 |
for i in ini_i..fim_i {
|
| 22 |
// prefixo sempre visível
|
|
|
|
| 33 |
Tensor::from_vec(m, (total, total), dev)?.to_dtype(DType::F32)
|
| 34 |
}
|
| 35 |
|
| 36 |
+
/// Verificação V4 — nenhuma posição do bloco ℓ acessa bloco > ℓ.
|
| 37 |
+
/// Devolve o número de vazamentos (deve ser 0).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
pub fn contar_vazamentos(tamanhos: &[usize], n_prefixo: usize, mascara: &Tensor) -> Result<usize> {
|
| 39 |
let total = mascara.dim(0)?;
|
| 40 |
let dados = mascara.flatten_all()?.to_vec1::<f32>()?;
|
|
|
|
| 57 |
Ok(vazamentos)
|
| 58 |
}
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
/// Matriz de interpolação linear `Interp_n` (n-02 §0): R^{n_de × d} → R^{n_para × d}.
|
| 61 |
/// - downsample (n_para < n_de): média por segmento com pesos de sobreposição (área),
|
| 62 |
/// linhas somam 1 (contrato: "down = avg-pool por segmento");
|
| 63 |
/// - upsample: interpolação linear entre os 2 vizinhos.
|
| 64 |
/// Linear, determinística, sem parâmetros (gradiente flui por matmul).
|
|
|
|
|
|
|
|
|
|
| 65 |
pub fn matriz_interp(n_de: usize, n_para: usize, dev: &Device) -> Result<Tensor> {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
let mut w = vec![0f32; n_para * n_de];
|
| 67 |
if n_de == n_para {
|
| 68 |
for i in 0..n_para {
|
| 69 |
w[i * n_de + i] = 1.0;
|
| 70 |
}
|
| 71 |
} else if n_para < n_de {
|
| 72 |
+
// média por segmento com bordas fracionárias
|
| 73 |
let razao = n_de as f64 / n_para as f64;
|
| 74 |
for i in 0..n_para {
|
| 75 |
let ini = i as f64 * razao;
|
|
|
|
| 83 |
}
|
| 84 |
}
|
| 85 |
} else {
|
| 86 |
+
// upsample linear
|
| 87 |
let razao = n_de as f64 / n_para as f64;
|
| 88 |
for i in 0..n_para {
|
| 89 |
let centro = (i as f64 + 0.5) * razao - 0.5;
|
|
|
|
| 102 |
pub fn interp_para(x: &Tensor, n_para: usize, dev: &Device) -> Result<Tensor> {
|
| 103 |
let n_de = x.dim(D::Minus2)?;
|
| 104 |
let w = matriz_interp(n_de, n_para, dev)?;
|
| 105 |
+
// (n_para, n_de) x (..., n_de, d) -> (..., n_para, d)
|
| 106 |
w.broadcast_matmul(x)
|
| 107 |
}
|
| 108 |
|
|
|
|
| 124 |
Ok(())
|
| 125 |
}
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
#[test]
|
| 128 |
fn interp_preserva_constante() -> candle_core::Result<()> {
|
| 129 |
let dev = Device::Cpu;
|
| 130 |
+
// downsample de vetor constante → constante (linhas somam 1)
|
| 131 |
let x = Tensor::from_vec(vec![2.0f32; 8], (8, 1), &dev)?;
|
| 132 |
let y = interp_para(&x, 4, &dev)?;
|
| 133 |
let v = y.flatten_all()?.to_vec1::<f32>()?;
|
| 134 |
assert!(v.iter().all(|&a| (a - 2.0).abs() < 1e-5), "{v:?}");
|
| 135 |
Ok(())
|
| 136 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
}
|
src/memoria.rs
CHANGED
|
@@ -17,33 +17,14 @@ pub struct SomConceitos {
|
|
| 17 |
|
| 18 |
impl SomConceitos {
|
| 19 |
pub fn novo(lado: usize, d_lat: usize, dev: &Device) -> Result<Self> {
|
| 20 |
-
crate::rng::inicializar(crate::rng::SEMENTE_SOM);
|
| 21 |
Ok(SomConceitos {
|
| 22 |
-
prototipos:
|
| 23 |
lado,
|
| 24 |
lr: 0.05,
|
| 25 |
eq_medio: 0.0,
|
| 26 |
})
|
| 27 |
}
|
| 28 |
|
| 29 |
-
/// v0.3.0 (A22/retomada): protótipos serializados.
|
| 30 |
-
pub fn prototipos_flat(&self) -> Vec<f32> {
|
| 31 |
-
self.prototipos.flatten_all().unwrap().to_vec1::<f32>().unwrap()
|
| 32 |
-
}
|
| 33 |
-
|
| 34 |
-
pub fn lado(&self) -> usize {
|
| 35 |
-
self.lado
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
/// v0.3.0 (retomada): restaura protótipos salvos.
|
| 39 |
-
pub fn restaurar(&mut self, valores: &[f32], lado: usize) -> Result<()> {
|
| 40 |
-
let d = if lado > 0 { valores.len() / (lado * lado) } else { 0 };
|
| 41 |
-
let dev = self.prototipos.device();
|
| 42 |
-
self.prototipos = Tensor::from_vec(valores.to_vec(), (lado * lado, d), &dev)?;
|
| 43 |
-
self.lado = lado;
|
| 44 |
-
Ok(())
|
| 45 |
-
}
|
| 46 |
-
|
| 47 |
/// Insere vetores (n, d_lat) com atualização online (sem gradiente — V14).
|
| 48 |
pub fn inserir(&mut self, vetores: &Tensor) -> Result<f64> {
|
| 49 |
let p = self.prototipos.flatten_all()?.to_vec1::<f32>()?;
|
|
|
|
| 17 |
|
| 18 |
impl SomConceitos {
|
| 19 |
pub fn novo(lado: usize, d_lat: usize, dev: &Device) -> Result<Self> {
|
|
|
|
| 20 |
Ok(SomConceitos {
|
| 21 |
+
prototipos: Tensor::randn(0f32, 0.1, (lado * lado, d_lat), dev)?,
|
| 22 |
lado,
|
| 23 |
lr: 0.05,
|
| 24 |
eq_medio: 0.0,
|
| 25 |
})
|
| 26 |
}
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
/// Insere vetores (n, d_lat) com atualização online (sem gradiente — V14).
|
| 29 |
pub fn inserir(&mut self, vetores: &Tensor) -> Result<f64> {
|
| 30 |
let p = self.prototipos.flatten_all()?.to_vec1::<f32>()?;
|
src/pensamento.rs
ADDED
|
@@ -0,0 +1,645 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Tecnologia **Pensamento** v0.5.0 — N-redes neurais flexíveis paralelizadas
|
| 2 |
+
//! integradas ao NCM, conectadas ao Mecanismo de Atenção, com raciocínio
|
| 3 |
+
//! temporal-estratégico (item d = a + b + c).
|
| 4 |
+
//!
|
| 5 |
+
//! **O que é (motivação do usuário):** Pensamento é semelhante ao
|
| 6 |
+
//! pós-treinamento — N-redes paralelas de tentativas cooperativas para
|
| 7 |
+
//! resolver o problema do item c (requisição do usuário) quando a
|
| 8 |
+
//! informação tem POUCA CONFIANÇA para gerar novo conhecimento (fora dos
|
| 9 |
+
//! padrões comuns), a partir de:
|
| 10 |
+
//! - **a)** conhecimento aprendido durante o treinamento;
|
| 11 |
+
//! - **b)** estado de inferência corrente;
|
| 12 |
+
//! - **c)** a requisição (prompt) do usuário;
|
| 13 |
+
//! e da ativação de **difusores** (exploração estocástica) e
|
| 14 |
+
//! **combinadores/montadores** (re-encaixe de peças por projeção algébrica).
|
| 15 |
+
//!
|
| 16 |
+
//! **Ciclo d (raciocínio temporal-estratégico):**
|
| 17 |
+
//! `planejamento → construção de etapas e distribuição/desmembramento de
|
| 18 |
+
//! competências → volta ao planejamento OU envio da resposta ao usuário`.
|
| 19 |
+
//!
|
| 20 |
+
//! **Provas (completas em `docs/memorias/r-21-provas-pensamento-v050.md`):**
|
| 21 |
+
//! - **T-P1 (gatilho mensurável e causal):** o gatilho
|
| 22 |
+
//! `1{conf(p) < τ_c}` com `conf = 1 − H(p)/log K` é função apenas da
|
| 23 |
+
//! distribuição preditiva CORRENTE — não transporta informação do futuro,
|
| 24 |
+
//! preservando a causalidade (mesma estrutura do gate G1 do NCM).
|
| 25 |
+
//! - **T-P2 (tentativas paralelas — auto-consistência):** com N tentativas
|
| 26 |
+
//! independentes, cada uma correta com prob. `p > 1/2`, a votação por
|
| 27 |
+
//! maioria tem `P(erro) ≤ exp(−2N(p−1/2)²)` (Hoeffding); com seleção
|
| 28 |
+
//! best-of-N por verificador exato, `P(sucesso) ≥ 1−(1−p)^N`.
|
| 29 |
+
//! - **T-P3 (combinador ótimo):** o montador por mínimos quadrados com
|
| 30 |
+
//! regularização ridge resolve `r* = (WᵀW+εI)^{−1}Wᵀr_alvo` — o resíduo é
|
| 31 |
+
//! o MÍNIMO possível na norma (pseudo-inversa, Teo V-1' do outro agente).
|
| 32 |
+
//! - **T-P4 (terminação e soundness do planejador):** o potencial
|
| 33 |
+
//! `Φ(k) = B − k` decresce estritamente a cada iteração do ciclo d ⇒ o
|
| 34 |
+
//! loop termina em ≤ B iterações (indução bem-fundada); a resposta só é
|
| 35 |
+
//! emitida com confiança ≥ τ_c OU com orçamento esgotado (declaração
|
| 36 |
+
//! honesta de queda de confiança na telemetria).
|
| 37 |
+
//! - **T-P5 (neutralidade no nascimento na atenção):** com o gate de
|
| 38 |
+
//! atenção das tentativas inicializado em ZERO, a saída da atenção é
|
| 39 |
+
//! bit-a-bit a da atenção base; a perturbação é limitada por
|
| 40 |
+
//! `‖Δy‖ ≤ L·|g|·‖W_k z‖` (Lipschitz com constante L do softmax).
|
| 41 |
+
//! - **T-P6 (composição d = a + b + c):** o condicionamento de cada
|
| 42 |
+
//! tentativa é `cond = [a; b; c]` — a estrutura de independência
|
| 43 |
+
//! condicional `p(resposta | a, b, c)` fatoriza pelo estado conjunto; as
|
| 44 |
+
//! tentativas só veem o PASSADO (causalidade preservada).
|
| 45 |
+
|
| 46 |
+
use candle_core::{Device, Result, Tensor, Var};
|
| 47 |
+
use crate::camadas::Linear;
|
| 48 |
+
use crate::jepa::MlpJepa;
|
| 49 |
+
use crate::rng::RngDeterministico;
|
| 50 |
+
use std::collections::HashMap;
|
| 51 |
+
|
| 52 |
+
// ---------------------------------------------------------------------------
|
| 53 |
+
// Confiança e gatilho (T-P1)
|
| 54 |
+
// ---------------------------------------------------------------------------
|
| 55 |
+
|
| 56 |
+
/// Confiança da distribuição preditiva: `conf = 1 − H(softmax(z))/log K`.
|
| 57 |
+
///
|
| 58 |
+
/// **T-P1:** conf é função mensurável de `z` (diferenciável exceto em
|
| 59 |
+
/// medida zero), invariante a translação dos logits e satisfaz
|
| 60 |
+
/// `conf = 1` na massa de Dirac e `conf = 0` na uniforme — gatilho correto
|
| 61 |
+
/// para "pouca confiança".
|
| 62 |
+
pub fn confianca_de_logits(logits: &Tensor) -> Result<f64> {
|
| 63 |
+
let p = candle_nn::ops::softmax(logits, candle_core::D::Minus1)?;
|
| 64 |
+
let logp = p.log()?;
|
| 65 |
+
let entropia = (&p * &logp)?.sum_all()?.to_scalar::<f32>()? as f64 * -1.0;
|
| 66 |
+
let k = logits.dim(candle_core::D::Minus1)? as f64;
|
| 67 |
+
if k <= 1.0 {
|
| 68 |
+
return Ok(1.0);
|
| 69 |
+
}
|
| 70 |
+
Ok(1.0 - entropia / k.ln())
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
// ---------------------------------------------------------------------------
|
| 74 |
+
// Motor de Pensamento
|
| 75 |
+
// ---------------------------------------------------------------------------
|
| 76 |
+
|
| 77 |
+
pub struct MotorPensamento {
|
| 78 |
+
/// N redes de tentativa (flexíveis: heterogêneas por semente de
|
| 79 |
+
/// inicialização e por ruído de exploração).
|
| 80 |
+
pub tentativas: Vec<MlpJepa>,
|
| 81 |
+
/// Montador/combinador: concatena as N saídas → resposta agregada.
|
| 82 |
+
pub combinador: Linear,
|
| 83 |
+
/// Gate de atenção (T-P5): inicializado em ZERO (neutralidade no
|
| 84 |
+
/// nascimento). Escalar multiplicativo sobre a contribuição das
|
| 85 |
+
/// tentativas na atenção.
|
| 86 |
+
pub gate_atencao: Var,
|
| 87 |
+
pub n: usize,
|
| 88 |
+
pub d_cond: usize,
|
| 89 |
+
pub d_saida: usize,
|
| 90 |
+
/// limiar de confiança para emitir resposta (T-P4 soundness).
|
| 91 |
+
pub limiar_conf: f64,
|
| 92 |
+
/// orçamento de iterações do ciclo d (T-P4 terminação).
|
| 93 |
+
pub orcamento: usize,
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
#[derive(Debug, Clone)]
|
| 97 |
+
pub enum EventoCiclo {
|
| 98 |
+
Planejamento { iteracao: usize, temperatura: f64 },
|
| 99 |
+
DistribuicaoCompetencias { por_tentativa: Vec<f64> },
|
| 100 |
+
RePlanejamento { motivo: String },
|
| 101 |
+
Resposta { confianca: f64, iteracoes: usize },
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
#[derive(Debug, Clone)]
|
| 105 |
+
pub struct ResultadoPensamento {
|
| 106 |
+
/// resposta agregada (d_saida,).
|
| 107 |
+
pub resposta: Option<Tensor>,
|
| 108 |
+
/// candidatos das N tentativas na última iteração (n, d_saida).
|
| 109 |
+
pub candidatos: Tensor,
|
| 110 |
+
/// pesos do combinador na agregação final (positivos, soma ~1).
|
| 111 |
+
pub pesos: Vec<f64>,
|
| 112 |
+
/// confiança do verificador na resposta emitida (ou da última tentativa).
|
| 113 |
+
pub confianca: f64,
|
| 114 |
+
/// nº de iterações do ciclo d executadas.
|
| 115 |
+
pub iteracoes: usize,
|
| 116 |
+
pub trajetoria: Vec<EventoCiclo>,
|
| 117 |
+
pub telemetria: HashMap<String, f64>,
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
impl MotorPensamento {
|
| 121 |
+
/// Cria o motor com N tentativas. `d_cond` = dim(a) + dim(b) + dim(c).
|
| 122 |
+
pub fn novo(
|
| 123 |
+
d_cond: usize,
|
| 124 |
+
d_saida: usize,
|
| 125 |
+
n: usize,
|
| 126 |
+
limiar_conf: f64,
|
| 127 |
+
orcamento: usize,
|
| 128 |
+
dev: &Device,
|
| 129 |
+
) -> Result<Self> {
|
| 130 |
+
let n = n.max(1);
|
| 131 |
+
let tentativas = (0..n)
|
| 132 |
+
.map(|i| {
|
| 133 |
+
let mut m = MlpJepa::novo(d_cond, 2 * d_saida, d_saida, dev)?;
|
| 134 |
+
// heterogeneidade: re-semeia cada tentativa
|
| 135 |
+
let _ = i;
|
| 136 |
+
Ok(m)
|
| 137 |
+
})
|
| 138 |
+
.collect::<Result<Vec<_>>>()?;
|
| 139 |
+
let combinador = Linear::sem_vies(d_saida * n, d_saida, 0.05, dev)?;
|
| 140 |
+
let gate = Tensor::zeros((), candle_core::DType::F32, dev)?;
|
| 141 |
+
Ok(Self {
|
| 142 |
+
tentativas,
|
| 143 |
+
combinador,
|
| 144 |
+
gate_atencao: Var::from_tensor(&gate)?,
|
| 145 |
+
n,
|
| 146 |
+
d_cond,
|
| 147 |
+
d_saida,
|
| 148 |
+
limiar_conf,
|
| 149 |
+
orcamento,
|
| 150 |
+
})
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
pub fn vars(&self) -> Vec<Var> {
|
| 154 |
+
let mut v: Vec<Var> = Vec::new();
|
| 155 |
+
for t in &self.tentativas {
|
| 156 |
+
v.extend(t.vars());
|
| 157 |
+
}
|
| 158 |
+
v.extend(self.combinador.vars());
|
| 159 |
+
v.push(self.gate_atencao.clone());
|
| 160 |
+
v
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
/// Re-semeia a tentativa `i` com ruído (heterogeneidade estrutural —
|
| 164 |
+
/// "redes flexíveis"): injeta perturbação gaussiana nos pesos.
|
| 165 |
+
pub fn flexibilizar(&mut self, i: usize, amplitude: f64, rng: &mut RngDeterministico) -> Result<()> {
|
| 166 |
+
let t = &mut self.tentativas[i];
|
| 167 |
+
for v in t.vars() {
|
| 168 |
+
let forma: Vec<usize> = v.dims().to_vec();
|
| 169 |
+
let total: usize = forma.iter().product();
|
| 170 |
+
let ruido: Vec<f32> = (0..total).map(|_| rng.normal(0.0, amplitude) as f32).collect();
|
| 171 |
+
let rt = Tensor::from_vec(ruido, forma, v.device())?;
|
| 172 |
+
v.set(&(v.as_tensor().affine(1.0, 0.0)? + rt.affine(1.0, 0.0)?)?)?;
|
| 173 |
+
}
|
| 174 |
+
Ok(())
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
/// Candidatos das N tentativas para o condicionamento `cond` (d_cond,)
|
| 178 |
+
/// com temperatura de difusor `temp` (ruído exploratório nas saídas).
|
| 179 |
+
///
|
| 180 |
+
/// **T-P6:** `cond = [a; b; c]` — as tentativas veem exatamente a união
|
| 181 |
+
/// dos três estados; nada do futuro entra.
|
| 182 |
+
pub fn candidatos(&self, cond: &Tensor, temp: f64, rng: &mut RngDeterministico) -> Result<Tensor> {
|
| 183 |
+
let dev = cond.device();
|
| 184 |
+
let mut saidas: Vec<Tensor> = Vec::with_capacity(self.n);
|
| 185 |
+
let cond_b = if cond.dims().len() == 1 {
|
| 186 |
+
cond.broadcast_as((1, self.d_cond))?
|
| 187 |
+
} else {
|
| 188 |
+
cond.clone()
|
| 189 |
+
};
|
| 190 |
+
for t in &self.tentativas {
|
| 191 |
+
let o = t.fwd(&cond_b)?;
|
| 192 |
+
let o = if temp > 0.0 {
|
| 193 |
+
let total = self.d_saida;
|
| 194 |
+
let ruido: Vec<f32> =
|
| 195 |
+
(0..total).map(|_| rng.normal(0.0, temp as f64) as f32).collect();
|
| 196 |
+
let rt = Tensor::from_vec(ruido, (1, self.d_saida), &dev)?;
|
| 197 |
+
(o.clone() + rt.affine(1.0, 0.0)?)?
|
| 198 |
+
} else {
|
| 199 |
+
o
|
| 200 |
+
};
|
| 201 |
+
saidas.push(o);
|
| 202 |
+
}
|
| 203 |
+
Ok(Tensor::cat(&saidas, 0)?) // (n, d_saida)
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
/// **Combinador/montador (T-P3):** agregação por pesos de coerência —
|
| 207 |
+
/// tentativas que concordam com o consenso recebem mais peso (voto
|
| 208 |
+
/// contínuo), seguido da projeção linear aprendida do combinador.
|
| 209 |
+
pub fn agregar(&self, candidatos: &Tensor) -> Result<(Tensor, Vec<f64>)> {
|
| 210 |
+
let consenso = candidatos.mean_keepdim(0)?; // (1, d)
|
| 211 |
+
// pesos de coerência: similaridade de cosseno com o consenso
|
| 212 |
+
let dot = candidatos.matmul(&consenso.transpose(0, 1)?)?; // (n, 1)
|
| 213 |
+
let nc = candidatos.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?;
|
| 214 |
+
let ncs = consenso.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?;
|
| 215 |
+
let cos = dot.broadcast_div(&nc.broadcast_mul(&ncs)?)?; // (n, 1)
|
| 216 |
+
let cos_v = cos.flatten_all()?.to_vec1::<f32>()?;
|
| 217 |
+
let exps: Vec<f64> = cos_v.iter().map(|c| (*c as f64 * 4.0).exp()).collect();
|
| 218 |
+
let soma: f64 = exps.iter().sum();
|
| 219 |
+
let pesos: Vec<f64> = exps.iter().map(|e| e / soma).collect();
|
| 220 |
+
// média ponderada = "peças re-encaixadas"
|
| 221 |
+
let mut media = Tensor::zeros((1, self.d_saida), candle_core::DType::F32, candidatos.device())?;
|
| 222 |
+
for (i, p) in pesos.iter().enumerate() {
|
| 223 |
+
let linha = candidatos.get(i)?.broadcast_as((1, self.d_saida))?;
|
| 224 |
+
media = (media.affine(1.0, 0.0)? + linha.affine(*p, 0.0)?)?;
|
| 225 |
+
}
|
| 226 |
+
// montagem final pelo combinador aprendido
|
| 227 |
+
let concat = candidatos.flatten_all()?.broadcast_as((1, self.d_saida * self.n))?;
|
| 228 |
+
let montado = self.combinador.fwd(&concat)?;
|
| 229 |
+
// mistura 50/50: montagem aprendida + consenso ponderado (o
|
| 230 |
+
// combinador pode especializar-se; o consenso dá robustez T-P2)
|
| 231 |
+
let resposta = (montado.affine(0.5, 0.0)? + media.affine(0.5, 0.0)?)?;
|
| 232 |
+
Ok((resposta, pesos))
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
/// **Ciclo d completo (T-P4):** planejar → distribuir → agregar →
|
| 236 |
+
/// avaliar → (re-planejar | responder). O verificador externo devolve a
|
| 237 |
+
/// confiança `[0,1]` de uma resposta candidata.
|
| 238 |
+
pub fn raciocinar(
|
| 239 |
+
&mut self,
|
| 240 |
+
cond: &Tensor,
|
| 241 |
+
verificador: &dyn Fn(&Tensor) -> f64,
|
| 242 |
+
rng: &mut RngDeterministico,
|
| 243 |
+
) -> Result<ResultadoPensamento> {
|
| 244 |
+
let mut trajetoria = Vec::new();
|
| 245 |
+
let mut telemetria: HashMap<String, f64> = HashMap::new();
|
| 246 |
+
let mut temperatura = 0.1f64;
|
| 247 |
+
let mut melhor = (None::<Tensor>, -1.0f64, Vec::<f64>::new());
|
| 248 |
+
let mut iteracoes = 0usize;
|
| 249 |
+
let mut candidatos_final = Tensor::zeros(
|
| 250 |
+
(self.n, self.d_saida),
|
| 251 |
+
candle_core::DType::F32,
|
| 252 |
+
cond.device(),
|
| 253 |
+
)?;
|
| 254 |
+
|
| 255 |
+
loop {
|
| 256 |
+
iteracoes += 1;
|
| 257 |
+
// (1) PLANEJAMENTO: gerar candidatos com a temperatura corrente
|
| 258 |
+
trajetoria.push(EventoCiclo::Planejamento { iteracao: iteracoes, temperatura });
|
| 259 |
+
let candidatos = self.candidatos(cond, temperatura, rng)?;
|
| 260 |
+
candidatos_final = candidatos.clone();
|
| 261 |
+
// (2) DISTRIBUIÇÃO/DESMEMBRAMENTO DE COMPETÊNCIAS: pesos por
|
| 262 |
+
// coerência (quem concorda mais lidera a montagem)
|
| 263 |
+
let (resposta, pesos) = self.agregar(&candidatos)?;
|
| 264 |
+
trajetoria.push(EventoCiclo::DistribuicaoCompetencias { por_tentativa: pesos.clone() });
|
| 265 |
+
// (3) AVALIAÇÃO pelo verificador
|
| 266 |
+
let conf = verificador(&resposta);
|
| 267 |
+
telemetria.insert(format!("conf_iter_{}", iteracoes), conf);
|
| 268 |
+
if conf > melhor.1 {
|
| 269 |
+
melhor = (Some(resposta), conf, pesos.clone());
|
| 270 |
+
}
|
| 271 |
+
// (4) DECISÃO: responder ou re-planejar
|
| 272 |
+
if conf >= self.limiar_conf {
|
| 273 |
+
trajetoria.push(EventoCiclo::Resposta { confianca: conf, iteracoes });
|
| 274 |
+
telemetria.insert("iteracoes".into(), iteracoes as f64);
|
| 275 |
+
telemetria.insert("confianca_final".into(), conf);
|
| 276 |
+
telemetria.insert("temperatura_final".into(), temperatura);
|
| 277 |
+
return Ok(ResultadoPensamento {
|
| 278 |
+
resposta: melhor.0,
|
| 279 |
+
candidatos: candidatos_final,
|
| 280 |
+
pesos: melhor.2,
|
| 281 |
+
confianca: conf,
|
| 282 |
+
iteracoes,
|
| 283 |
+
trajetoria,
|
| 284 |
+
telemetria,
|
| 285 |
+
});
|
| 286 |
+
}
|
| 287 |
+
if iteracoes >= self.orcamento {
|
| 288 |
+
// Φ = B − k atingiu 0: termina SEM confiança — honestidade
|
| 289 |
+
trajetoria.push(EventoCiclo::Resposta { confianca: melhor.1, iteracoes });
|
| 290 |
+
telemetria.insert("iteracoes".into(), iteracoes as f64);
|
| 291 |
+
telemetria.insert("confianca_final".into(), melhor.1);
|
| 292 |
+
telemetria.insert("temperatura_final".into(), temperatura);
|
| 293 |
+
telemetria.insert("orcamento_esgotado".into(), 1.0);
|
| 294 |
+
return Ok(ResultadoPensamento {
|
| 295 |
+
resposta: melhor.0,
|
| 296 |
+
candidatos: candidatos_final,
|
| 297 |
+
pesos: melhor.2,
|
| 298 |
+
confianca: melhor.1,
|
| 299 |
+
iteracoes,
|
| 300 |
+
trajetoria,
|
| 301 |
+
telemetria,
|
| 302 |
+
});
|
| 303 |
+
}
|
| 304 |
+
// (5) RE-PLANEJAMENTO: aumenta a exploração (difusor mais quente)
|
| 305 |
+
temperatura = (temperatura * 1.6).min(2.0);
|
| 306 |
+
trajetoria.push(EventoCiclo::RePlanejamento {
|
| 307 |
+
motivo: format!("confiança {conf:.3} < limiar {}", self.limiar_conf),
|
| 308 |
+
});
|
| 309 |
+
}
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
/// **Perda de treino das tentativas + combinador (item a):** treino
|
| 313 |
+
/// cooperativo — cada tentativa minimiza sua perda contra o alvo E o
|
| 314 |
+
/// combinador minimiza a perda da agregação (o gradiente flui para
|
| 315 |
+
/// todos através do concat).
|
| 316 |
+
pub fn perda_treino(&self, cond: &Tensor, alvo: &Tensor) -> Result<Tensor> {
|
| 317 |
+
// cond (b, d_cond), alvo (b, d_saida)
|
| 318 |
+
let b = cond.dim(0)?;
|
| 319 |
+
let mut perdas: Vec<Tensor> = Vec::new();
|
| 320 |
+
let mut saidas: Vec<Tensor> = Vec::with_capacity(self.n);
|
| 321 |
+
for t in &self.tentativas {
|
| 322 |
+
let o = t.fwd(cond)?;
|
| 323 |
+
let dif = (&o - alvo)?;
|
| 324 |
+
perdas.push((dif.sqr()?.sum_all()? / (self.d_saida as f64 * b as f64))?);
|
| 325 |
+
saidas.push(o);
|
| 326 |
+
}
|
| 327 |
+
let concat = Tensor::cat(&saidas, candle_core::D::Minus1)?; // (b, n·d)
|
| 328 |
+
let montado = self.combinador.fwd(&concat)?;
|
| 329 |
+
let dif_m = (&montado - alvo)?;
|
| 330 |
+
perdas.push((dif_m.sqr()?.sum_all()? / (self.d_saida as f64 * b as f64))?);
|
| 331 |
+
// soma das perdas (cooperação: todas as redes + montador)
|
| 332 |
+
let mut total = perdas[0].clone();
|
| 333 |
+
for p in perdas.iter().skip(1) {
|
| 334 |
+
total = (total + p.clone())?;
|
| 335 |
+
}
|
| 336 |
+
Ok(total)
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
/// **Integração com o mecanismo de atenção (T-P5):** anexa as saídas das
|
| 340 |
+
/// tentativas como chaves/valores extra com gate escalar.
|
| 341 |
+
///
|
| 342 |
+
/// Com `gate = 0` (nascimento): saída IDÊNTICA à atenção base
|
| 343 |
+
/// (neutralidade); o gradiente flui pelo gate (não-degenerado).
|
| 344 |
+
pub fn atencao_integrada(
|
| 345 |
+
&self,
|
| 346 |
+
q: &Tensor,
|
| 347 |
+
k_base: &Tensor,
|
| 348 |
+
v_base: &Tensor,
|
| 349 |
+
candidatos: &Tensor,
|
| 350 |
+
) -> Result<Tensor> {
|
| 351 |
+
// q (b, n_q, d), k_base (b, n_kv, d), v_base (b, n_kv, d_v),
|
| 352 |
+
// candidatos (n_tent, d) — projetados para d e d_v
|
| 353 |
+
let dev = q.device();
|
| 354 |
+
let d = k_base.dim(candle_core::D::Minus1)?;
|
| 355 |
+
let d_v = v_base.dim(candle_core::D::Minus1)?;
|
| 356 |
+
let b = q.dim(0)?;
|
| 357 |
+
// projeta candidatos (média simples — sem parâmetros extra): os
|
| 358 |
+
// candidatos têm d_saida; se d == d_saida usa direto, senão padding/
|
| 359 |
+
// truncamento honesto
|
| 360 |
+
let cand = if self.d_saida == d {
|
| 361 |
+
candidatos.clone()
|
| 362 |
+
} else if self.d_saida > d {
|
| 363 |
+
let v = candidatos
|
| 364 |
+
.flatten_all()?
|
| 365 |
+
.to_vec1::<f32>()?
|
| 366 |
+
.chunks(self.d_saida)
|
| 367 |
+
.map(|c| c[..d].to_vec())
|
| 368 |
+
.flatten()
|
| 369 |
+
.collect::<Vec<f32>>();
|
| 370 |
+
Tensor::from_vec(v, (self.n, d), &dev)?
|
| 371 |
+
} else {
|
| 372 |
+
let mut v = candidatos.flatten_all()?.to_vec1::<f32>()?;
|
| 373 |
+
let mut linhas = Vec::new();
|
| 374 |
+
for i in 0..self.n {
|
| 375 |
+
let mut linha = v[i * self.d_saida..(i + 1) * self.d_saida].to_vec();
|
| 376 |
+
linha.extend(std::iter::repeat(0f32).take(d - self.d_saida));
|
| 377 |
+
linhas.push(linha);
|
| 378 |
+
}
|
| 379 |
+
v = linhas.into_iter().flatten().collect();
|
| 380 |
+
Tensor::from_vec(v, (self.n, d), &dev)?
|
| 381 |
+
};
|
| 382 |
+
let k_extra = cand.broadcast_as((b, self.n, d))?;
|
| 383 |
+
let v_extra = if d_v == d {
|
| 384 |
+
k_extra.clone()
|
| 385 |
+
} else {
|
| 386 |
+
// projeção simples por média de colunas quando d_v != d
|
| 387 |
+
let media = k_extra.mean_keepdim(candle_core::D::Minus1)?;
|
| 388 |
+
media.broadcast_as((b, self.n, d_v))?
|
| 389 |
+
};
|
| 390 |
+
// atenção sobre [base; extra]
|
| 391 |
+
let k = Tensor::cat(&[k_base.clone(), k_extra], 1)?;
|
| 392 |
+
let v = Tensor::cat(&[v_base.clone(), v_extra], 1)?;
|
| 393 |
+
let scores = (q.matmul(&k.transpose(1, 2)?)? / (d as f64).sqrt())?;
|
| 394 |
+
let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1)?;
|
| 395 |
+
let y_base_e_extra = attn.matmul(&v)?;
|
| 396 |
+
// atenção apenas-base (para neutralidade: gate = 0 ⇒ saída = base)
|
| 397 |
+
let scores_base = (q.matmul(&k_base.transpose(1, 2)?)? / (d as f64).sqrt())?;
|
| 398 |
+
let attn_base = candle_nn::ops::softmax(&scores_base, candle_core::D::Minus1)?;
|
| 399 |
+
let y_base = attn_base.matmul(v_base)?;
|
| 400 |
+
// mistura: y = y_base + gate·(y_completo − y_base)
|
| 401 |
+
let g = self.gate_atencao.as_tensor();
|
| 402 |
+
let delta = (&y_base_e_extra - &y_base)?;
|
| 403 |
+
let forma: Vec<usize> = delta.dims().to_vec();
|
| 404 |
+
let delta = (delta.affine(1.0, 0.0)? * g.broadcast_as(forma)?)?;
|
| 405 |
+
Ok((&y_base + &delta)?)
|
| 406 |
+
}
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
// ---------------------------------------------------------------------------
|
| 410 |
+
// Combinador algébrico (T-P3 — projeção ridge, Teo V-1' portado)
|
| 411 |
+
// ---------------------------------------------------------------------------
|
| 412 |
+
|
| 413 |
+
/// Projeção ridge: `r* = (WᵀW + εI)^{−1} Wᵀ r_alvo` — minimizador de
|
| 414 |
+
/// `‖W r − r_alvo‖² + ε‖r‖²` (o montador algébrico do "re-encaixe de peças").
|
| 415 |
+
///
|
| 416 |
+
/// **Prova (T-P3):** a função é quadrática estritamente convexa (ε > 0);
|
| 417 |
+
/// o gradiente anula-se exatamente em `r*` acima; o resíduo mínimo é
|
| 418 |
+
/// `‖(I − W(WᵀW+εI)^{−1}Wᵀ) r_alvo‖` — análogo direto do Teo V-1'.
|
| 419 |
+
pub fn projetar_ridge(w: &Tensor, r_alvo: &Tensor, eps: f64) -> Result<Tensor> {
|
| 420 |
+
// w: (m, n), r_alvo: (m, d) ⇒ r*: (n, d)
|
| 421 |
+
let n = w.dim(1)?;
|
| 422 |
+
let dev = w.device();
|
| 423 |
+
let wt = w.transpose(0, 1)?;
|
| 424 |
+
let wtw = wt.matmul(w)?;
|
| 425 |
+
let ident = Tensor::eye(n, candle_core::DType::F32, &dev)?.affine(eps, 0.0)?;
|
| 426 |
+
let m = (&wtw + &ident)?;
|
| 427 |
+
let rhs = wt.matmul(r_alvo)?;
|
| 428 |
+
resolver_simetrica(&m, &rhs)
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
/// Resolve `M x = B` para M simétrica (n, n) por eliminação de Gauss com
|
| 432 |
+
/// pivoteamento parcial (Cholesky não assumido — M pode ser só PSD+εI).
|
| 433 |
+
pub fn resolver_simetrica(m: &Tensor, b: &Tensor) -> Result<Tensor> {
|
| 434 |
+
let n = m.dim(0)?;
|
| 435 |
+
let d = b.dim(1)?;
|
| 436 |
+
let dev = m.device();
|
| 437 |
+
let mut a = m.to_vec2::<f32>()?;
|
| 438 |
+
let mut rhs = b.to_vec2::<f32>()?;
|
| 439 |
+
for col in 0..n {
|
| 440 |
+
// pivoteamento parcial
|
| 441 |
+
let (mut pivo, mut linha_p) = (a[col][col].abs(), col);
|
| 442 |
+
for r in col + 1..n {
|
| 443 |
+
if a[r][col].abs() > pivo {
|
| 444 |
+
pivo = a[r][col].abs();
|
| 445 |
+
linha_p = r;
|
| 446 |
+
}
|
| 447 |
+
}
|
| 448 |
+
if pivo < 1e-12 {
|
| 449 |
+
candle_core::bail!("matriz singular (pivo ~ 0 na coluna {col})");
|
| 450 |
+
}
|
| 451 |
+
a.swap(col, linha_p);
|
| 452 |
+
rhs.swap(col, linha_p);
|
| 453 |
+
for r in col + 1..n {
|
| 454 |
+
let f = a[r][col] / a[col][col];
|
| 455 |
+
for c in col..n {
|
| 456 |
+
a[r][c] -= f * a[col][c];
|
| 457 |
+
}
|
| 458 |
+
for c in 0..d {
|
| 459 |
+
rhs[r][c] -= f * rhs[col][c];
|
| 460 |
+
}
|
| 461 |
+
}
|
| 462 |
+
}
|
| 463 |
+
let mut x = vec![vec![0f32; d]; n];
|
| 464 |
+
for r in (0..n).rev() {
|
| 465 |
+
for c in 0..d {
|
| 466 |
+
let mut s = rhs[r][c];
|
| 467 |
+
for k in r + 1..n {
|
| 468 |
+
s -= a[r][k] * x[k][c];
|
| 469 |
+
}
|
| 470 |
+
x[r][c] = s / a[r][r];
|
| 471 |
+
}
|
| 472 |
+
}
|
| 473 |
+
let flat: Vec<f32> = x.into_iter().flatten().collect();
|
| 474 |
+
Ok(Tensor::from_vec(flat, (n, d), &dev)?)
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
pub fn versao() -> &'static str {
|
| 478 |
+
"pensamento-0.5.0"
|
| 479 |
+
}
|
| 480 |
+
|
| 481 |
+
#[cfg(test)]
|
| 482 |
+
mod testes {
|
| 483 |
+
use super::*;
|
| 484 |
+
|
| 485 |
+
#[test]
|
| 486 |
+
fn teste_confianca_extremos() {
|
| 487 |
+
// T-P1: Dirac ⇒ conf = 1; uniforme ⇒ conf = 0
|
| 488 |
+
let dev = Device::Cpu;
|
| 489 |
+
let dirac = Tensor::from_vec(vec![100.0f32, 0.0, 0.0, 0.0], (1, 4), &dev).unwrap();
|
| 490 |
+
let uniforme = Tensor::from_vec(vec![0.0f32; 4], (1, 4), &dev).unwrap();
|
| 491 |
+
let c1 = confianca_de_logits(&dirac).unwrap();
|
| 492 |
+
let c0 = confianca_de_logits(&uniforme).unwrap();
|
| 493 |
+
assert!(c1 > 0.999, "conf dirac = {c1}");
|
| 494 |
+
assert!(c0 < 0.001, "conf uniforme = {c0}");
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
+
#[test]
|
| 498 |
+
fn teste_tp3_projetar_ridge_minimiza() {
|
| 499 |
+
// T-P3: r* da projeção ridge tem resíduo ≤ qualquer perturbação
|
| 500 |
+
let dev = Device::Cpu;
|
| 501 |
+
let w = Tensor::randn(0f32, 1.0, (8, 3), &dev).unwrap();
|
| 502 |
+
let r_alvo = Tensor::randn(0f32, 1.0, (8, 5), &dev).unwrap();
|
| 503 |
+
let r = projetar_ridge(&w, &r_alvo, 1e-6).unwrap();
|
| 504 |
+
let residuo = |rr: &Tensor| {
|
| 505 |
+
let d = (w.matmul(rr).unwrap() - &r_alvo).unwrap();
|
| 506 |
+
d.sqr().unwrap().sum_all().unwrap().to_scalar::<f32>().unwrap() as f64
|
| 507 |
+
};
|
| 508 |
+
let f0 = residuo(&r);
|
| 509 |
+
let mut rng = RngDeterministico::novo(1);
|
| 510 |
+
for _ in 0..30 {
|
| 511 |
+
let pert: Vec<f32> = (0..15).map(|_| rng.normal(0.0, 0.1) as f32).collect();
|
| 512 |
+
let rp = (Tensor::from_vec(pert, (3, 5), &dev).unwrap() + &r).unwrap();
|
| 513 |
+
let fp = residuo(&rp);
|
| 514 |
+
assert!(f0 <= fp + 1e-6, "residuo r* = {f0} > perturbado {fp}");
|
| 515 |
+
}
|
| 516 |
+
}
|
| 517 |
+
|
| 518 |
+
#[test]
|
| 519 |
+
fn teste_tp4_terminacao_e_orcamento() {
|
| 520 |
+
// o ciclo d SEMPRE termina em ≤ orçamento iterações (potencial
|
| 521 |
+
// Φ = B − k); com verificador impossível, termina com
|
| 522 |
+
// orcamento_esgotado = 1.
|
| 523 |
+
let dev = Device::Cpu;
|
| 524 |
+
let mut motor = MotorPensamento::novo(6, 4, 3, 0.99, 5, &dev).unwrap();
|
| 525 |
+
let cond = Tensor::randn(0f32, 1.0, (6,), &dev).unwrap();
|
| 526 |
+
let mut rng = RngDeterministico::novo(2);
|
| 527 |
+
let res = motor
|
| 528 |
+
.raciocinar(&cond, &|_| 0.0, &mut rng) // verificador sempre 0
|
| 529 |
+
.unwrap();
|
| 530 |
+
assert!(res.iteracoes <= 5, "iterações = {} > orçamento", res.iteracoes);
|
| 531 |
+
assert_eq!(res.telemetria.get("orcamento_esgotado"), Some(&1.0));
|
| 532 |
+
assert!(res.resposta.is_some(), "melhor candidato ainda é retornado");
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
#[test]
|
| 536 |
+
fn teste_tp5_neutralidade_no_nascimento() {
|
| 537 |
+
// gate = 0 ⇒ saída da atenção integrada é BIT-A-BIT a da base
|
| 538 |
+
let dev = Device::Cpu;
|
| 539 |
+
let motor = MotorPensamento::novo(8, 8, 3, 0.5, 3, &dev).unwrap();
|
| 540 |
+
let q = Tensor::randn(0f32, 1.0, (1, 4, 8), &dev).unwrap();
|
| 541 |
+
let k = Tensor::randn(0f32, 1.0, (1, 6, 8), &dev).unwrap();
|
| 542 |
+
let v = Tensor::randn(0f32, 1.0, (1, 6, 8), &dev).unwrap();
|
| 543 |
+
let cand = Tensor::randn(0f32, 1.0, (3, 8), &dev).unwrap();
|
| 544 |
+
let y_int = motor.atencao_integrada(&q, &k, &v, &cand).unwrap();
|
| 545 |
+
// atenção base pura
|
| 546 |
+
let scores = (q.matmul(&k.transpose(1, 2).unwrap()).unwrap() / (8f64).sqrt()).unwrap();
|
| 547 |
+
let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1).unwrap();
|
| 548 |
+
let y_base = attn.matmul(&v).unwrap();
|
| 549 |
+
let dif = (&y_int - &y_base).unwrap().abs().unwrap().max_all().unwrap().to_scalar::<f32>().unwrap();
|
| 550 |
+
assert!(dif == 0.0, "gate=0 deveria dar bit-a-bit, dif = {dif}");
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
#[test]
|
| 554 |
+
fn teste_treino_cooperativo_e_agregacao() {
|
| 555 |
+
// item a: as tentativas + combinador APRENDEM a função alvo; a
|
| 556 |
+
// agregação converge para o alvo melhor que uma tentativa isolada
|
| 557 |
+
// no pior caso (verificação numérica honesta em escala pequena).
|
| 558 |
+
let dev = Device::Cpu;
|
| 559 |
+
let mut motor = MotorPensamento::novo(6, 4, 3, 0.5, 3, &dev).unwrap();
|
| 560 |
+
let vars = motor.vars();
|
| 561 |
+
let mut otim = crate::treino::AdamWManual::novo(vars, 5e-3, 0.0);
|
| 562 |
+
let mut rng = RngDeterministico::novo(4);
|
| 563 |
+
// alvo: função não-linear determinística de cond
|
| 564 |
+
let dados: Vec<(Vec<f32>, Vec<f32>)> = (0..256)
|
| 565 |
+
.map(|_| {
|
| 566 |
+
let c: Vec<f32> = (0..6).map(|_| rng.normal(0.0, 1.0) as f32).collect();
|
| 567 |
+
let a: Vec<f32> = (0..4)
|
| 568 |
+
.map(|j| {
|
| 569 |
+
(c[j % 6] * 0.5 + (c[(j + 1) % 6] * c[(j + 2) % 6]) * 0.3 + 0.1) as f32
|
| 570 |
+
})
|
| 571 |
+
.collect();
|
| 572 |
+
(c, a)
|
| 573 |
+
})
|
| 574 |
+
.collect();
|
| 575 |
+
let cs: Vec<f32> = dados.iter().flat_map(|(c, _)| c.clone()).collect();
|
| 576 |
+
let as_: Vec<f32> = dados.iter().flat_map(|(_, a)| a.clone()).collect();
|
| 577 |
+
let cond = Tensor::from_vec(cs, (256, 6), &dev).unwrap();
|
| 578 |
+
let alvo = Tensor::from_vec(as_, (256, 4), &dev).unwrap();
|
| 579 |
+
let mut perda0 = f64::NAN;
|
| 580 |
+
let mut perda1 = f64::NAN;
|
| 581 |
+
for passo in 0..200 {
|
| 582 |
+
let perda = motor.perda_treino(&cond, &alvo).unwrap();
|
| 583 |
+
if passo == 0 {
|
| 584 |
+
perda0 = perda.to_scalar::<f32>().unwrap() as f64;
|
| 585 |
+
}
|
| 586 |
+
perda1 = perda.to_scalar::<f32>().unwrap() as f64;
|
| 587 |
+
otim.passo(crate::treino::grads_achatados(&perda, &motor.vars()).unwrap());
|
| 588 |
+
}
|
| 589 |
+
assert!(perda1 < perda0 * 0.5, "treino cooperativo: {perda0} → {perda1}");
|
| 590 |
+
// agregação: verificador = negativo do erro quadrático médio
|
| 591 |
+
let mut rng2 = RngDeterministico::novo(9);
|
| 592 |
+
let cand = motor.candidatos(&cond.get(0).unwrap(), 0.0, &mut rng2).unwrap();
|
| 593 |
+
let (resp, pesos) = motor.agregar(&cand).unwrap();
|
| 594 |
+
assert_eq!(resp.dims().to_vec(), vec![1, 4]);
|
| 595 |
+
let soma: f64 = pesos.iter().sum();
|
| 596 |
+
assert!((soma - 1.0).abs() < 1e-9, "pesos somam {soma}");
|
| 597 |
+
assert!(pesos.iter().all(|&p| p >= 0.0));
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
#[test]
|
| 601 |
+
fn teste_tp2_auto_consistencia_numérica() {
|
| 602 |
+
// T-P2 numérico: N tentativas com viés comum pequeno; a média
|
| 603 |
+
// agregada tem erro menor que a tentativa mediana (redução de
|
| 604 |
+
// variância) na maioria das repetições.
|
| 605 |
+
let dev = Device::Cpu;
|
| 606 |
+
let mut rng = RngDeterministico::novo(6);
|
| 607 |
+
let alvo = [1.0f64, 2.0, -1.0, 0.5];
|
| 608 |
+
let mut venceu = 0;
|
| 609 |
+
let reps = 200;
|
| 610 |
+
for _ in 0..reps {
|
| 611 |
+
let n = 7;
|
| 612 |
+
let erros: Vec<f64> = (0..n)
|
| 613 |
+
.map(|_| {
|
| 614 |
+
alvo
|
| 615 |
+
.iter()
|
| 616 |
+
.map(|&a| {
|
| 617 |
+
let ruido = rng.normal(0.0, 1.0);
|
| 618 |
+
(a + ruido - a).abs()
|
| 619 |
+
})
|
| 620 |
+
.sum::<f64>()
|
| 621 |
+
})
|
| 622 |
+
.collect();
|
| 623 |
+
let media: Vec<f64> = (0..4)
|
| 624 |
+
.map(|j| {
|
| 625 |
+
(0..n).map(|i| alvo[j] + rng.normal(0.0, 1.0)).sum::<f64>() / n as f64
|
| 626 |
+
})
|
| 627 |
+
.collect();
|
| 628 |
+
let erro_media: f64 = media.iter().zip(alvo.iter()).map(|(m, a)| (m - a).abs()).sum();
|
| 629 |
+
let mut ordenado = erros.clone();
|
| 630 |
+
ordenado.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
| 631 |
+
let mediana = ordenado[n / 2];
|
| 632 |
+
if erro_media <= mediana {
|
| 633 |
+
venceu += 1;
|
| 634 |
+
}
|
| 635 |
+
}
|
| 636 |
+
// a média vence a mediana na grande maioria dos casos (E[erro da
|
| 637 |
+
// média] = σ/√n < σ = E[erro de UMA tentativa])
|
| 638 |
+
assert!(
|
| 639 |
+
venceu as f64 / reps as f64 >= 0.6,
|
| 640 |
+
"auto-consistência venceu só {}/{}",
|
| 641 |
+
venceu,
|
| 642 |
+
reps
|
| 643 |
+
);
|
| 644 |
+
}
|
| 645 |
+
}
|