v0.9.0: análise/prova/teste das críticas Qwen3.8 lote 2 (r-34) — SIMD com despacho em runtime bit-a-bit (X-1), oráculo candle_nn::optim::AdamW (nosso 1,53× mais rápido), Thompson Sampling (regret 0,0019 vs UCB1 0,0156), GNG por entropia com piso (T-G1'), JEPA de fração adaptativa (bit-a-bit com 1 braço), configs tipadas, entrada numpy (2008× na fronteira), avaliação com dados reais — 104/104 gates, 111/111 cargo
Browse files- Cargo.lock +102 -1
- Cargo.toml +12 -13
- README.md +5 -16
- docs/memorias/r-34-analise-provas-qwen-v090.md +590 -359
- pyproject.toml +3 -3
- resultados/suite_v090_final.json +96 -59
- resultados/telemetria_fase_c.jsonl +61 -167
- resultados/treino_v090.json +75 -174
- scripts/70_suite_v090.py +221 -0
- scripts/71_treino_v090.py +101 -130
- scripts/72_publicar_v090.py +166 -0
- src/bin/bench_v090.rs +278 -0
- src/camadas.rs +16 -162
- src/controlador.rs +219 -0
- src/fnh.rs +3 -357
- src/gerador.rs +5 -14
- src/jepa.rs +1775 -0
- src/lib.rs +0 -0
- src/moe_redirecionavel.rs +1202 -0
- src/pensamento.rs +1192 -0
- src/rng.rs +81 -0
- src/simd.rs +792 -0
- src/som4d.rs +167 -34
- src/telemetria.rs +19 -0
- src/tokenizador.rs +3 -38
- src/treino.rs +102 -23
- src/verificador.rs +0 -73
Cargo.lock
CHANGED
|
@@ -302,6 +302,17 @@ dependencies = [
|
|
| 302 |
"syn 2.0.119",
|
| 303 |
]
|
| 304 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 305 |
[[package]]
|
| 306 |
name = "dyn-stack"
|
| 307 |
version = "0.13.2"
|
|
@@ -723,16 +734,19 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
|
| 723 |
|
| 724 |
[[package]]
|
| 725 |
name = "khtst-rust"
|
| 726 |
-
version = "0.
|
| 727 |
dependencies = [
|
| 728 |
"anyhow",
|
| 729 |
"built",
|
| 730 |
"candle-core",
|
| 731 |
"candle-nn",
|
| 732 |
"crossbeam-channel",
|
|
|
|
| 733 |
"log",
|
|
|
|
| 734 |
"pyo3",
|
| 735 |
"pyo3-async-runtimes",
|
|
|
|
| 736 |
"pyo3-log",
|
| 737 |
"pythonize",
|
| 738 |
"rand 0.8.8",
|
|
@@ -783,6 +797,16 @@ version = "0.2.3"
|
|
| 783 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 784 |
checksum = "fc04a4c58212d57930a24bf47d3fa87485264a3a054e9c10e042eb373573ad3c"
|
| 785 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 786 |
[[package]]
|
| 787 |
name = "memchr"
|
| 788 |
version = "2.8.3"
|
|
@@ -836,6 +860,21 @@ dependencies = [
|
|
| 836 |
"syn 2.0.119",
|
| 837 |
]
|
| 838 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 839 |
[[package]]
|
| 840 |
name = "nom"
|
| 841 |
version = "7.1.3"
|
|
@@ -856,6 +895,15 @@ dependencies = [
|
|
| 856 |
"num-traits",
|
| 857 |
]
|
| 858 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 859 |
[[package]]
|
| 860 |
name = "num-traits"
|
| 861 |
version = "0.2.19"
|
|
@@ -876,6 +924,21 @@ dependencies = [
|
|
| 876 |
"libc",
|
| 877 |
]
|
| 878 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 879 |
[[package]]
|
| 880 |
name = "once_cell"
|
| 881 |
version = "1.21.4"
|
|
@@ -934,6 +997,15 @@ version = "1.15.0"
|
|
| 934 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 935 |
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
| 936 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 937 |
[[package]]
|
| 938 |
name = "ppv-lite86"
|
| 939 |
version = "0.2.21"
|
|
@@ -1016,6 +1088,12 @@ dependencies = [
|
|
| 1016 |
"target-lexicon",
|
| 1017 |
]
|
| 1018 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1019 |
[[package]]
|
| 1020 |
name = "pyo3-ffi"
|
| 1021 |
version = "0.23.5"
|
|
@@ -1171,6 +1249,12 @@ dependencies = [
|
|
| 1171 |
"bitflags",
|
| 1172 |
]
|
| 1173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1174 |
[[package]]
|
| 1175 |
name = "rayon"
|
| 1176 |
version = "1.12.0"
|
|
@@ -1237,6 +1321,12 @@ version = "0.8.11"
|
|
| 1237 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1238 |
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
| 1239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1240 |
[[package]]
|
| 1241 |
name = "rustix"
|
| 1242 |
version = "1.1.5"
|
|
@@ -1381,6 +1471,17 @@ version = "0.11.1"
|
|
| 1381 |
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1382 |
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
| 1383 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1384 |
[[package]]
|
| 1385 |
name = "syn"
|
| 1386 |
version = "2.0.119"
|
|
|
|
| 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.9.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",
|
| 750 |
"pyo3-log",
|
| 751 |
"pythonize",
|
| 752 |
"rand 0.8.8",
|
|
|
|
| 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"
|
|
|
|
| 1088 |
"target-lexicon",
|
| 1089 |
]
|
| 1090 |
|
| 1091 |
+
[[package]]
|
| 1092 |
+
name = "pyo3-built"
|
| 1093 |
+
version = "0.5.0"
|
| 1094 |
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
| 1095 |
+
checksum = "35ee655adc94166665a1d714b439e27857dd199b947076891d6a17d32d396cde"
|
| 1096 |
+
|
| 1097 |
[[package]]
|
| 1098 |
name = "pyo3-ffi"
|
| 1099 |
version = "0.23.5"
|
|
|
|
| 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 — críticas Qwen3.8 lote
|
| 6 |
license = "Apache-2.0"
|
| 7 |
|
| 8 |
[lib]
|
|
@@ -10,7 +10,14 @@ name = "khtst_rust"
|
|
| 10 |
crate-type = ["cdylib", "rlib"]
|
| 11 |
|
| 12 |
[dependencies]
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
candle-core = "0.11"
|
| 15 |
candle-nn = "0.11"
|
| 16 |
crossbeam-channel = "0.5"
|
|
@@ -22,20 +29,12 @@ thiserror = "2"
|
|
| 22 |
log = "0.4"
|
| 23 |
rand = "0.8"
|
| 24 |
|
| 25 |
-
# --- FFI Python (feature "python" — v0.9.0: opcional, era incondicional) ---
|
| 26 |
-
pyo3 = { version = "0.23", features = ["abi3-py310"], optional = true }
|
| 27 |
-
pythonize = { version = "0.23", optional = true }
|
| 28 |
-
pyo3-log = { version = "0.12", optional = true }
|
| 29 |
-
pyo3-async-runtimes = { version = "0.23", optional = true, features = ["tokio-runtime"] }
|
| 30 |
-
tokio = { version = "1", features = ["rt-multi-thread"], optional = true }
|
| 31 |
-
|
| 32 |
[build-dependencies]
|
| 33 |
built = "0.8"
|
| 34 |
|
| 35 |
[features]
|
| 36 |
-
default = ["
|
| 37 |
-
|
| 38 |
-
ponte_async = ["python", "dep:pyo3-async-runtimes", "dep:tokio"]
|
| 39 |
|
| 40 |
[profile.release]
|
| 41 |
opt-level = 3
|
|
|
|
| 1 |
[package]
|
| 2 |
name = "khtst-rust"
|
| 3 |
+
version = "0.9.0"
|
| 4 |
edition = "2021"
|
| 5 |
+
description = "KHTST_RUST — provas e testes das críticas Qwen3.8 (lote v0.9.0): SIMD com despacho em runtime bit-a-bit, oráculo candle-nn AdamW, Thompson Sampling, GNG por entropia, JEPA de fração adaptativa, configs tipadas, entrada numpy"
|
| 6 |
license = "Apache-2.0"
|
| 7 |
|
| 8 |
[lib]
|
|
|
|
| 10 |
crate-type = ["cdylib", "rlib"]
|
| 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"
|
|
|
|
| 29 |
log = "0.4"
|
| 30 |
rand = "0.8"
|
| 31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
[build-dependencies]
|
| 33 |
built = "0.8"
|
| 34 |
|
| 35 |
[features]
|
| 36 |
+
default = ["ponte_async"]
|
| 37 |
+
ponte_async = ["pyo3-async-runtimes", "tokio"]
|
|
|
|
| 38 |
|
| 39 |
[profile.release]
|
| 40 |
opt-level = 3
|
README.md
CHANGED
|
@@ -1,19 +1,6 @@
|
|
| 1 |
-
# KHTST_RUST v0.
|
| 2 |
-
|
| 3 |
-
> **v0.
|
| 4 |
-
> (arquitetura/complexidade) analisado, provado e testado em `r-34`:
|
| 5 |
-
> **skew O(n²) diferenciável** via `index_add` (919× mais rápido em n=128,
|
| 6 |
-
> memória 2.335× menor, bit-a-bit — F-13/F-14), **calibrar zero-cópia**
|
| 7 |
-
> (1,56×, bit-a-bit, com a premissa "kernels SIMD" REFUTADA na CPU por
|
| 8 |
-
> análise do kernel do candle — F-15), **`forward` canônico** (bug v080-1
|
| 9 |
-
> fora da superfície pública; oráculos preservados), **FFI isolada em
|
| 10 |
-
> `feature = "python"`** (lib.rs 105 kB → 1,25 kB; `cargo test
|
| 11 |
-
> --no-default-features` roda 107/107 SEM Python; overhead medido 17,6%),
|
| 12 |
-
> **newtypes de domínio** com construtores validados (T-V1/T-V2 — errata
|
| 13 |
-
> da proposta `pub struct P(pub f64)`). **97/97 gates** (85 herdados +
|
| 14 |
-
> 12 novos) + treino REAL Wikipédia PT-BR íntegro no build v0.9.0.
|
| 15 |
-
|
| 16 |
-
> **v0.6.0**: **verificador NCM calibrado** substitui o oráculo
|
| 17 |
> do Pensamento (V-1..V-4 — soundness sem acesso ao alvo), **treino conjunto
|
| 18 |
> Pensamento↔backbone** (fase C: gradiente bidirecional, PCGrad, acoplador
|
| 19 |
> duplex AC-1..AC-3), **MoEs de codificação/decodificação redirecionáveis com
|
|
@@ -21,6 +8,8 @@
|
|
| 21 |
> (M-R1..M-R5), e **autoajuste por telemetria** de todos os hiperparâmetros
|
| 22 |
> do Pensamento (A-1..A-5) com **seleção de arquitetura por UCB1** e loop
|
| 23 |
> iterativo com parada marginal. Provas: `docs/memorias/r-23…r-27`.
|
|
|
|
|
|
|
| 24 |
|
| 25 |
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`, ADBA (atenção adaptativa no processamento paralelo) e Ponte Duplex (comunicação bidirecional simultânea entre modelos).
|
| 26 |
|
|
|
|
| 1 |
+
# KHTST_RUST v0.6.0 — Verificador NCM + Treino Conjunto + MoEs Redirecionáveis + Autoajuste
|
| 2 |
+
|
| 3 |
+
> **v0.6.0 (este ciclo)**: **verificador NCM calibrado** substitui o oráculo
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
> do Pensamento (V-1..V-4 — soundness sem acesso ao alvo), **treino conjunto
|
| 5 |
> Pensamento↔backbone** (fase C: gradiente bidirecional, PCGrad, acoplador
|
| 6 |
> duplex AC-1..AC-3), **MoEs de codificação/decodificação redirecionáveis com
|
|
|
|
| 8 |
> (M-R1..M-R5), e **autoajuste por telemetria** de todos os hiperparâmetros
|
| 9 |
> do Pensamento (A-1..A-5) com **seleção de arquitetura por UCB1** e loop
|
| 10 |
> iterativo com parada marginal. Provas: `docs/memorias/r-23…r-27`.
|
| 11 |
+
> Veredito consolidado: **46/46 gates verdes** (script 59) + **54/54 testes
|
| 12 |
+
> cargo**.
|
| 13 |
|
| 14 |
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`, ADBA (atenção adaptativa no processamento paralelo) e Ponte Duplex (comunicação bidirecional simultânea entre modelos).
|
| 15 |
|
docs/memorias/r-34-analise-provas-qwen-v090.md
CHANGED
|
@@ -1,405 +1,636 @@
|
|
| 1 |
-
# r-34 — Análise, Provas e Testes das Críticas
|
| 2 |
|
| 3 |
**Escopo**: o usuário pediu *analisar, provar e testar* o segundo lote de
|
| 4 |
-
críticas do agente Qwen3.8 (
|
| 5 |
-
mantendo o princípio da casa: **nenhuma
|
| 6 |
-
removida**; toda mudança com prova matemática,
|
| 7 |
-
computacional; provas em markdown; medições honestas
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
| 15 |
|
| 16 |
**Veredito sumário**:
|
| 17 |
|
| 18 |
-
| # | Crítica | Veredito |
|
| 19 |
-
|---|---------
|
| 20 |
-
| 1 |
|
| 21 |
-
|
|
| 22 |
-
|
|
| 23 |
-
|
|
| 24 |
-
|
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
|
| 27 |
---
|
| 28 |
|
| 29 |
-
## §
|
| 30 |
-
|
| 31 |
-
### 2.1 Análise da crítica
|
| 32 |
-
|
| 33 |
-
**Válido**: `lib.rs` tinha **104.558 bytes** com 42 `#[pyfunction]`, 5
|
| 34 |
-
`#[pyclass]`, o `#[pymodule]` inteiro e toda a cola Python
|
| 35 |
-
(`carregar_tokenizador`, `tensor_de_2d`, `py_err`, `callback`) — violação
|
| 36 |
-
real de SRP/SoP: lógica de domínio e FFI no mesmo arquivo; compilação
|
| 37 |
-
incremental lenta (qualquer toque na FFI recompila a fachada inteira).
|
| 38 |
-
|
| 39 |
-
**Impreciso na crítica**: (a) lib.rs **não continha** `#[cfg(test)]` — os
|
| 40 |
-
testes de invariantes já viviam inline nos módulos de domínio (verificado:
|
| 41 |
-
`grep '#\[cfg(test)\]' lib.rs` = 0 ocorrências); a mistura
|
| 42 |
-
"ta7_equivalencia_bit_a_bit com demo_qwen_v080" era entre ARQUIVOS
|
| 43 |
-
(testes no módulo, demos na FFI), não dentro do lib.rs; (b) a "Prova
|
| 44 |
-
Lógica" do Qwen afirma que o compilador "analisa dependências a nível de
|
| 45 |
-
crate e módulo" — impreciso: o cargo recompila por *crate* com granularidade
|
| 46 |
-
de *feature*; o ganho real e mensurável é **não compilar nem linkar** a
|
| 47 |
-
família pyo3 quando a feature `python` está desligada (§2.3).
|
| 48 |
-
|
| 49 |
-
### 2.2 Prova lógica (A-1: separação de dependências) ∎
|
| 50 |
-
|
| 51 |
-
**Enunciado**: com a FFI isolada em `#[cfg(feature = "python")] pub mod
|
| 52 |
-
python` e as dependências pyo3 marcadas `optional = true`, a suíte de
|
| 53 |
-
testes de domínio compila e executa sem que o crate pyo3 (nem numpy-python,
|
| 54 |
-
pyo3-log, pyo3-async-runtimes, tokio) seja sequer baixado-compilado.
|
| 55 |
-
|
| 56 |
-
**Prova**: a dependência sintática de `khtst-rust` (lib) para pyo3 existe
|
| 57 |
-
unicamente através de (i) `src/python.rs` e (ii) `src/treino.rs`
|
| 58 |
-
(`CallbackPy`). (i) é eliminável por `#[cfg]` (o item some da árvore de
|
| 59 |
-
módulos quando a feature está desligada); (ii) é eliminável por
|
| 60 |
-
re-tipagem: `CallbackPy = Option<Py<PyAny>>` foi substituído pelo tipo de
|
| 61 |
-
domínio `CallbackTreino = Option<Arc<dyn Fn(&serde_json::Value) + Send +
|
| 62 |
-
Sync>>` — `Py<PyAny>` é `Send + Sync`, logo o callable Python é
|
| 63 |
-
embrulhável num closure desse tipo NA FFI (adaptador
|
| 64 |
-
`callback_treino_de_py`), sem que o tipo de domínio mencione pyo3. Com (i)+(ii),
|
| 65 |
-
o grafo de `use` de todo módulo de domínio é livre de pyo3; como o rustc
|
| 66 |
-
só compila crates presentes no grafo de dependências resolvido pelas
|
| 67 |
-
features ativas, `cargo test --no-default-features` compila
|
| 68 |
-
khtst-rust+candle sem pyo3. ∎ **Verificação executável**: `cargo test
|
| 69 |
-
--no-default-features` → 105+2 = **107/107 verdes** (3 execuções
|
| 70 |
-
idênticas); a suíte de integração `tests/provas.rs` (G5–G20) incluída.
|
| 71 |
-
|
| 72 |
-
### 2.3 Medição honesta (corrige a alegação "≈40%")
|
| 73 |
-
|
| 74 |
-
Builds frios (target dirs separados, mesma máquina, perfil dev):
|
| 75 |
-
|
| 76 |
-
| Configuração | tempo |
|
| 77 |
-
|---|---|
|
| 78 |
-
| `cargo test --no-default-features --no-run` (domínio) | **70 s** |
|
| 79 |
-
| `cargo test --no-run` (python + ponte_async) | **85 s** |
|
| 80 |
|
| 81 |
-
|
| 82 |
-
alegação do Qwen não é teorema — depende do grafo de crates e da máquina;
|
| 83 |
-
a nossa é a medida local, com candle-core compilado igualmente nos dois
|
| 84 |
-
cenários (diferença isolada à família pyo3). O ganho estrutural além do
|
| 85 |
-
tempo: `lib.rs` 104.558 B → **1.250 B** (razão 83,6×); a FFI inteira
|
| 86 |
-
(112.410 B) agora é uma unidade coerede editável isoladamente.
|
| 87 |
|
| 88 |
-
|
|
|
|
| 89 |
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
-
##
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
**
|
| 106 |
-
|
| 107 |
-
lê os valores e `from_vec` cria um tensor-FOLHA desconectado; o gradiente
|
| 108 |
-
∂L/∂θ ficaria **exatamente 0**. É a mesma classe do bug v080-1 (r-33 §5),
|
| 109 |
-
agora proposta justamente no módulo onde a v0.8.0 tinha resolvido isso com
|
| 110 |
-
a matriz de seleção (o comentário do código avisava: "construção
|
| 111 |
-
DIFERENCIÁVEL... sem slice_assign"). Trocar Θ(n⁴) diferenciável por Θ(n²)
|
| 112 |
-
não-diferenciável é remover a funcionalidade SOTA (aprendizado das
|
| 113 |
-
rotações) — vedado pela regra da casa.
|
| 114 |
-
|
| 115 |
-
**A correção**: `index_add` sobre um vetor de zeros — operação RASTREADA
|
| 116 |
-
(`BackpropOp::IndexAdd` no fonte do candle, op.rs:87), disponível para
|
| 117 |
-
tensores 1-D com índices U32 no backend CPU (mod.rs:2945).
|
| 118 |
-
|
| 119 |
-
### 3.2 Teo F-13 (equivalência bit-a-bit com a v0.8.0) ∎
|
| 120 |
-
|
| 121 |
-
**Enunciado**: seja `A_novo` o skew construído por
|
| 122 |
-
`zeros(n²).index_add(sup, θ).index_add(inf, −θ)` e `A_ref` o da v0.8.0
|
| 123 |
-
(`θ(1,m) @ S` seguido de `U − Uᵀ`). Para todo θ finito (após clamp),
|
| 124 |
-
`A_novo = A_ref` **bit-a-bit**.
|
| 125 |
-
|
| 126 |
-
**Prova**: na referência, cada posição p do produto tem exatamente um
|
| 127 |
-
termo não-nulo: `U_p = Σ_k θ_k·S[k,p] = θ_{k(p)} + (m−1)·(±0,0)`; em
|
| 128 |
-
IEEE-754 com arredondamento ao mais próximo, `x + (±0,0) = x` para todo x
|
| 129 |
-
finito (e o caso x = −0,0: `(−0,0)+(+0,0) = +0,0`, idem no index_add:
|
| 130 |
-
`(+0,0)+(−0,0) = +0,0` — mesma saída), qualquer que seja a ordem de soma
|
| 131 |
-
do gemm. No novo, `A_p = 0,0 + θ_{k(p)} = θ_{k(p)}` (index_add CPU:
|
| 132 |
-
`dst[i] += src[j]`). Logo `U = A_novo,sup` bit-a-bit; `−θ` por `affine(−1,0)`
|
| 133 |
-
é exato (negar o bit de sinal); `A = U − Uᵀ` é a MESMA operação nas duas
|
| 134 |
-
versões; a distribuição dos pares (i,j) na ordem lexicográfica é idêntica
|
| 135 |
-
à da construção de S. ∎ **Teste f13**: n ∈ {2,4,8,16,17}, θ ∈ [−1,4, 1,4]
|
| 136 |
-
(exercita clamp e espelho), `to_bits()` idêntico em todas as entradas e
|
| 137 |
-
`A + Aᵀ = 0` exato.
|
| 138 |
-
|
| 139 |
-
### 3.3 Teo F-14 (custo e gradiente) ∎
|
| 140 |
-
|
| 141 |
-
1. **Espaço**: novo aloca `n²` f32 (zeros) + `2m` u32 (índices) + `m` f32
|
| 142 |
-
(−θ) = Θ(n²) bytes, contra Θ(m·n²) = Θ(n⁴) da referência. n=256:
|
| 143 |
-
256 KiB + 384 KiB contra **8,6 GB** — a referência é inviável em RAM
|
| 144 |
-
comum; o novo executa (teste f14 constrói A(256) sem esforço).
|
| 145 |
-
2. **Tempo**: o custo dominante da referência é o gemm (1,m)×(m,n²):
|
| 146 |
-
Θ(n⁴) FLOPs + o preenchimento de S (Θ(n⁴) escritas). O novo custa
|
| 147 |
-
Θ(m) adições + Θ(n²) zero-fill. Medição (§3.4) confirma a razão
|
| 148 |
-
assintótica crescendo com n.
|
| 149 |
-
3. **Gradiente preservado**: `index_add`, `affine`, `reshape` são
|
| 150 |
-
rastreadas ⇒ ∂L/∂θ ≠ 0 (teste f14 com θ≠0; e no nascimento θ=0 a perda
|
| 151 |
-
LINEAR em entrada única R[0,1] dá dR[0,1]/dθ₀ = 1 — grafo vivo). ∎
|
| 152 |
-
|
| 153 |
-
*Nota honesta (registrada nos comentários do teste)*: com L = ‖R‖²_F e
|
| 154 |
-
θ = 0, o gradiente é nulo **por matemática** (R = I ⇒ L = n + ‖A‖²_F,
|
| 155 |
-
mínimo; A skew tem traço nulo) — a primeira versão do teste f14 usava
|
| 156 |
-
essa perda e "falhava" corretamente; o teste foi reformulado com perda
|
| 157 |
-
que exercita o gradiente. Erro meu, catalogado no §9.
|
| 158 |
-
|
| 159 |
-
### 3.4 Medição (release, mediana de 3 execuções da demo; 5 reps cada)
|
| 160 |
-
|
| 161 |
-
| n | tempo ref (v0.8.0) | tempo novo | speedup | memória S: novo | razão |
|
| 162 |
-
|---|---|---|---|---|---|
|
| 163 |
-
| 16 | 21,6 µs | 3,2 µs | 3,8× | 122.880 : 3.424 B | 36× |
|
| 164 |
-
| 64 | 3,27 ms | 37 µs | 98× | 33,0 MB : 56,7 KB | 583× |
|
| 165 |
-
| 128 | 89,1 ms | 101 µs | **919×** | 508 MB : 223 KB | **2.335×** |
|
| 166 |
-
|
| 167 |
-
Valores bit-a-bit idênticos em todos os n (gate `q1_skew_bits_identicos`).
|
| 168 |
|
| 169 |
---
|
| 170 |
|
| 171 |
-
## §
|
| 172 |
-
|
| 173 |
-
###
|
| 174 |
-
|
| 175 |
-
A crítica
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
`
|
| 202 |
-
|
| 203 |
-
(
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
`
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
|
| 218 |
---
|
| 219 |
|
| 220 |
-
## §
|
| 221 |
-
|
| 222 |
-
###
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
**
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
### 5.2 O que a crítica acerta: a materialização
|
| 240 |
-
|
| 241 |
-
O problema REAL do código v0.8.0 era `flatten_all().to_vec1::<f32>()` —
|
| 242 |
-
cópia de O(|w|+|x|) floats (9,4 MB por chamada no cenário medido) para a
|
| 243 |
-
RAM, + 2 laços. A correção adotada elimina a cópia **mantendo** o laço
|
| 244 |
-
rápido: acesso direto ao storage (`Tensor::storage_and_layout()` +
|
| 245 |
-
`CpuStorage::as_slice::<f32>()`, ambas API pública do candle) com varredura
|
| 246 |
-
`chunks_exact(d)` — sem divisão modular por elemento (k%d custa ~20 ciclos
|
| 247 |
-
e bloqueia auto-vetorização — segunda medição: 2,4× mais lento antes
|
| 248 |
-
desta correção).
|
| 249 |
-
|
| 250 |
-
**Teo F-15 (bit-a-bit e domínio) ∎**
|
| 251 |
-
1. **Ordem de comparações idêntica**: a referência percorre r=0..R, c=0..C
|
| 252 |
-
atualizando `wmax[c]` com `if a > wmax[c]`; a nova percorre
|
| 253 |
-
`chunks_exact(d)` — a mesma sequência linear de elementos, o mesmo
|
| 254 |
-
predicado por acumulador, na mesma ordem ⇒ mesmos resultados bit-a-bit
|
| 255 |
-
(seleção por máximo é exata, sem aritmética).
|
| 256 |
-
2. **Piso preservado**: acumuladores inicializados em 1e-5 (como a v0.8.0)
|
| 257 |
-
⇒ `max(1e-5, max_r |w|)` idêntico. A proposta Qwen NÃO tinha o piso:
|
| 258 |
-
canal nulo ⇒ `w_max = 0` ⇒ escala `0^{−(1−α)} = ∞` (regressão real,
|
| 259 |
-
capturada no teste sq1 com canais zeros).
|
| 260 |
-
3. **EMA preservada em f64**: a proposta computava `affine` em f32
|
| 261 |
-
(β·o e (1−β)·n arredondados a f32 antes da soma) — mudaria os bits a
|
| 262 |
-
cada passo; a v0.9.0 mantém a acumulação f64 com UMA conversão final,
|
| 263 |
-
idêntica à v0.8.0.
|
| 264 |
-
4. **Guard preservado**: `x.dims().last() == d_in` (senão xmax fica no piso).
|
| 265 |
-
5. **NaN (diferença documentada)**: o laço ignora NaN (predicado `>` é
|
| 266 |
-
falso); o fallback tensor propaga NaN — semântica estrita no caminho
|
| 267 |
-
acelerado. ∎
|
| 268 |
-
|
| 269 |
-
### 5.3 Medição (release, mediana de 3; w=(512,512), x=(4096,512))
|
| 270 |
-
|
| 271 |
-
| Variante | tempo | notas |
|
| 272 |
|---|---|---|
|
| 273 |
-
|
|
| 274 |
-
|
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
|
|
|
|
|
|
|
|
|
| 281 |
|
| 282 |
---
|
| 283 |
|
| 284 |
-
## §
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
|
| 286 |
-
|
|
|
|
|
|
|
|
|
|
| 287 |
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
decorativa" e "tipagem com refinamento".)
|
| 295 |
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
|
| 301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
|
| 303 |
-
|
| 304 |
-
`tipos` satisfaz `0 ≤ v ≤ 1` e `v ≠ NaN`.
|
| 305 |
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
|
| 314 |
-
|
| 315 |
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
|
| 324 |
-
|
| 325 |
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
|
| 332 |
---
|
| 333 |
|
| 334 |
-
## §
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
|
| 349 |
---
|
| 350 |
|
| 351 |
-
## §
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
3
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
Python (107/107); overhead Python medido 17,6% (não 40%).
|
| 361 |
-
5. **newtypes**: T-V1/T-V2 provados e testados; errata da proposta Qwen
|
| 362 |
-
(campo público não prova nada).
|
| 363 |
-
6. **Suíte**: 85/85 gates herdados v0.8.0 + 12 novos = **97/97**
|
| 364 |
-
(suite_v090_final.json); cargo 107/107 × 3 execuções (determinismo).
|
| 365 |
-
7. **Dados reais**: pipeline A/B/C completo em Wikipédia PT-BR (1.121
|
| 366 |
-
sequências, build v0.9.0): fase C CE 3,9500 → 3,9293 (decrescente —
|
| 367 |
-
o registro v0.7.0 havia terminado 3,98, ACIMA do inicial); FNH com
|
| 368 |
-
punição real: gate abre (0,941), θ aprende (0,0426), δ_ort = 2,1e−07
|
| 369 |
-
(SO(n) exato); árvore de pesos invariantes OK. Leitura honesta: 180
|
| 370 |
-
passos de fase conjunta em CPU é orçamento de fumaça — a demonstração
|
| 371 |
-
é de INTEGRIDADE do pipeline com o build v0.9.0 e dos gates, não de
|
| 372 |
-
qualidade de geração (amostras greedy colapsam neste orçamento).
|
| 373 |
|
| 374 |
---
|
| 375 |
|
| 376 |
-
## §
|
| 377 |
|
| 378 |
| ID | Onde | Causa-raiz | Correção |
|
| 379 |
|----|------|-----------|----------|
|
| 380 |
-
| v090-1
|
| 381 |
-
| v090-2 |
|
| 382 |
-
| v090-3
|
| 383 |
-
| v090-4 |
|
| 384 |
-
| v090-5 |
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# r-34 — Análise, Provas e Testes das Críticas do Agente Qwen3.8 — Lote 2 (v0.9.0)
|
| 2 |
|
| 3 |
**Escopo**: o usuário pediu *analisar, provar e testar* o segundo lote de
|
| 4 |
+
críticas do agente Qwen3.8 (4 pontos críticos de engenharia + 4 propostas
|
| 5 |
+
A–D + plano de ação de 5 itens), mantendo o princípio da casa: **nenhuma
|
| 6 |
+
funcionalidade SOTA eficaz é removida**; toda mudança com prova matemática,
|
| 7 |
+
lógica e de lógica computacional; provas em markdown; **medições honestas**
|
| 8 |
+
(com dados reais); autocrítica catalogada.
|
| 9 |
+
|
| 10 |
+
**Metodologia** (herdada da r-33): (i) verificação factual de cada premissa
|
| 11 |
+
contra o código-fonte REAL e a API real do candle 0.11; (ii) prova simbólica
|
| 12 |
+
do que sobrevive a (i); (iii) implementação **aditiva** com kernels
|
| 13 |
+
bit-a-bit idênticos quando substituem caminhos existentes; (iv) testes de
|
| 14 |
+
falsificação + determinismo; (v) benchmark A/B mediano + avaliação em dados
|
| 15 |
+
reais (Wikipédia PT-BR); (vi) o que a medição REFUTA é documentado como
|
| 16 |
+
refutado — não incorporado por inércia.
|
| 17 |
|
| 18 |
**Veredito sumário**:
|
| 19 |
|
| 20 |
+
| # | Crítica/Proposta | Veredito | Fatos decisivos |
|
| 21 |
+
|---|------------------|----------|-----------------|
|
| 22 |
+
| 1 | Parsing de config por strings | **PARCIALMENTE VÁLIDA** (arquivo citado não existe; config principal JÁ tipado; demos tinham defeito REAL de robustez) | `src/python/som.rs` inexistente; `config.rs` usa `#[derive(Deserialize)]` desde v0.3; demos usavam `unwrap_or` silencioso — corrigido |
|
| 23 |
+
| 2 | AdamW manual "impede fusão" | **REFUTADA** (e invertida) | `candle_nn::optim::AdamW` emite ~8 kernels eager/variável; nosso loop é 1 passada fundida — **1,53× mais rápido** (medição); oráculo de equivalência ε < 1e-4 |
|
| 24 |
+
| 3 | Serialização PyO3 | **VÁLIDA no gargalo certo** | numpy (rust-numpy) era dependência DECLARADA e NUNCA usada; fronteira lista→Vec custa **2008×** mais que numpy (5,83 ms vs 0,003 ms/524k tokens) — caminhos `_np` adicionados |
|
| 25 |
+
| 4 | Alinhamento SIMD 64 B | **REFUTADA em parte + erratas técnicas** | `#[repr(align(64))]` em struct com `Vec` NÃO alinha o heap; alinhamento medido não vale nada em workload streaming (±3%); RUSTFLAGS não cabe em `[profile]` |
|
| 26 |
+
| A | Árvore heap/Fenwick | **NÃO APLICÁVEL** (prova) | Nossa árvore não é BST binária de busca — é árvore de módulos (12 nós, arestas duplas); Amdahl: agregar custa µs num pipeline de segundos |
|
| 27 |
+
| B | SOM: entropia + Lyapunov + SoA | **MISTO**: Lyapunov-global ERRATA teórica; entropia como gatilho único SOFRE FOME (medição) — corrigida com piso; SoA "~8×" REFUTADA por medição (4 variantes testadas) | ver §6 |
|
| 28 |
+
| C | Thompson p/ MoE + IB p/ JEPA | **IDEIAS VÁLIDAS, REUBICADAS** | TS pertence à camada de SELEÇÃO (bandit), não ao roteamento por token (quebraria determinismo); implementado como seletor alternativo ao UCB1; IB conectado à perda preditiva (T-J1) |
|
| 29 |
+
| D | Monomorfização/DAG/precisão dinâmica | **REFUTADAS por auditoria+medição** | 6 sítios `dyn` todos por-lote (1e-7 relativo); candle é eager (sem fusor); bf16 em CPU candle é **3× MAIS LENTO** (0,34×) |
|
| 30 |
+
|
| 31 |
+
**Gates consolidados**: suite v0.9.0 = **104/104** (legado v0.8.0 85/85
|
| 32 |
+
preservado + novos blocos); cargo **111/111** (99 herdados + 12 novos), 3×
|
| 33 |
+
consecutivas.
|
| 34 |
|
| 35 |
---
|
| 36 |
|
| 37 |
+
## §1 — Crítica 1: parsing de configuração (overhead de strings)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
+
### 1.1 Verificação factual
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
A crítica afirma que "em `src/python/som.rs`, a configuração é parseada via
|
| 42 |
+
`match k.as_str()` dentro de um loop sobre um `serde_json::Value`".
|
| 43 |
|
| 44 |
+
1. **`src/python/som.rs` NÃO EXISTE** neste projeto (verificado por varredura
|
| 45 |
+
completa; a organização é `src/*.rs` com bindings em `src/lib.rs`).
|
| 46 |
+
2. **O config principal já é tipado**: `src/config.rs` usa exclusivamente
|
| 47 |
+
`#[derive(Deserialize)]` com `#[serde(default)]` (MoE, MTP, Modelo, Gsq,
|
| 48 |
+
Ncm, Adba, Ponte, Treino, TelemetriaCfg, Dados, MemoriaCfg, ConfigKhtst) —
|
| 49 |
+
exatamente o que o "plano de ação item 2" propõe implementar.
|
| 50 |
+
3. **O padrão `match k.as_str()` existia** — mas apenas nas **funções demo do
|
| 51 |
+
binding Python** (`treinar_som4d`, `treinar_gng`, `treinar_jepa`), chamadas
|
| 52 |
+
**1× por invocação** (setup, não laço quente).
|
| 53 |
|
| 54 |
+
### 1.2 O defeito REAL (que a crítica acertou de raspão)
|
| 55 |
+
|
| 56 |
+
O padrão das demos não era um problema de *performance* (Amdahl: parse único
|
| 57 |
+
de ~10 chaves ≈ µs num treino de minutos) — era um problema de
|
| 58 |
+
**robustez silenciosa**:
|
| 59 |
+
|
| 60 |
+
```rust
|
| 61 |
+
"nx" => c.nx = val.as_u64().unwrap_or(4) as usize, // v0.8.0
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
Passar `{"nx": "grande"}` (string) fazia `as_u64()` devolver `None` e o
|
| 65 |
+
`unwrap_or(4)` **engolia o erro e usava o default** — o usuário nunca sabia
|
| 66 |
+
que sua configuração foi ignorada. Este é o custo real de parse dinâmico: não
|
| 67 |
+
são os ciclos de comparação de string, é a **perda da checagem de tipo**.
|
| 68 |
|
| 69 |
+
### 1.3 Correção implementada (aditiva)
|
| 70 |
+
|
| 71 |
+
Structs tipadas `CfgSom4DJ`, `CfgGngJ`, `CfgJepaJ` com `#[serde(default)]` +
|
| 72 |
+
`parse_cfg_tipado::<T>()`: JSON inválido ou **tipo errado agora é
|
| 73 |
+
`PyValueError` explícito**; config válido comporta-se EXATAMENTE como antes
|
| 74 |
+
(defaults idênticos, chaves desconhecidas ignoradas como antes). Gates
|
| 75 |
+
`c1_cfg_valido_funciona` + `c1_tipo_errado_rejeitado` (2/2).
|
| 76 |
+
|
| 77 |
+
*Nota lógica*: o `lint_config` continua usando `serde_json::Value` — por
|
| 78 |
+
DESIGN (é um linter sobre chaves arbitrárias; tipá-lo destruiria a função).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
|
| 80 |
---
|
| 81 |
|
| 82 |
+
## §2 — Crítica 2: "autograd e otimização manual"
|
| 83 |
+
|
| 84 |
+
### 2.1 Verificação factual (3 erros na crítica)
|
| 85 |
+
|
| 86 |
+
1. A crítica cita `candle-nn::optimizers::AdamW` — **o módulo não existe**;
|
| 87 |
+
o caminho real é `candle_nn::optim::AdamW` (verificado no fonte do crate).
|
| 88 |
+
2. A crítica alega que o manual "impede a fusão de operadores que frameworks
|
| 89 |
+
como candle-nn ou tch-rs fariam automaticamente via XLA/Graph compilers".
|
| 90 |
+
**candle é eager-mode e não tem compilador de grafo** (nem XLA, nem IR de
|
| 91 |
+
fusão — verificado no fonte). Não há o que "impedir".
|
| 92 |
+
3. A implicação de que o AdamW do framework seria mais rápido é **invertida**
|
| 93 |
+
pela medição (§2.4).
|
| 94 |
+
|
| 95 |
+
### 2.2 Análise do `candle_nn::optim::AdamW` (o oráculo)
|
| 96 |
+
|
| 97 |
+
O código do crate (optim.rs, v0.11) executa POR VARIÁVEL POR PASSO:
|
| 98 |
+
`next_m = m*β₁ + g*(1−β₁)`, `next_v = v*β₂ + g²*(1−β₂)`, `m_hat`, `v_hat`,
|
| 99 |
+
`sqrt`, `div`, `mul`, `sub` — **~8 kernels eager de tensor**, cada um lendo/
|
| 100 |
+
escrevendo o array completo. O nosso `AdamWManual::passo` faz **uma única
|
| 101 |
+
passada fundida** por variável (m, v, saída no mesmo loop). A "fusão" que a
|
| 102 |
+
crítica pede já existe no nosso código — no único nível em que ela pode
|
| 103 |
+
existir numa stack eager: **o loop**.
|
| 104 |
+
|
| 105 |
+
### 2.3 Teo X-3 (equivalência matemática) ∎
|
| 106 |
+
|
| 107 |
+
**Afirmação**: com os mesmos hiperparâmetros, `AdamWManual::passo` e
|
| 108 |
+
`candle_nn::optim::AdamW::step` computam a mesma atualização de AdamW com
|
| 109 |
+
decaimento desacoplado.
|
| 110 |
+
|
| 111 |
+
**Prova**: candle-nn computa
|
| 112 |
+
`θ ← θ·(1 − lr·λ) − lr·m̂/(√v̂ + ε)`; o nosso computa
|
| 113 |
+
`θ ← θ − lr·(m̂/(√v̂ + ε) + λ·θ)`. Por distributividade:
|
| 114 |
+
`θ − lr·adj − lr·λ·θ = θ·(1 − lr·λ) − lr·adj` — **identidade algébrica**
|
| 115 |
+
(as correções de viés `m̂ = m/bc₁`, `v̂ = v/bc₂` são idênticas nos dois).
|
| 116 |
+
A diferença é apenas a ORDEM de avaliaç��o em ponto flutuante: candle-nn
|
| 117 |
+
multiplica pelo recíproco escalado; nós dividimos; e a associação dos termos
|
| 118 |
+
difere — logo a igualdade é **em ε, não bit-a-bit** ( floats não são
|
| 119 |
+
associativos). ∎
|
| 120 |
+
|
| 121 |
+
**Teste oráculo** (`adamw_oraculo_candle_nn`): regressão linear convexa, 60
|
| 122 |
+
passos, mesmos hiperparâmetros, SEM clip (compara fórmula, não o mecanismo de
|
| 123 |
+
segurança) — divergência final `max|Δ|/escala < 1e-4`, e ambos convergem ao
|
| 124 |
+
mínimo exato. O oráculo INDEPENDENTE do framework confirma a fórmula.
|
| 125 |
+
|
| 126 |
+
### 2.4 Medição (16 variáveis × 100k parâmetros, 30 passos, mediana de 5)
|
| 127 |
+
|
| 128 |
+
| Otimizador | ms/passo |
|
| 129 |
+
|---|---|
|
| 130 |
+
| `AdamWManual` (nosso, agora com kernel SIMD interno) | **8,1–9,3** |
|
| 131 |
+
| `candle_nn::optim::AdamW` | 13,7–14,2 |
|
| 132 |
+
|
| 133 |
+
**Nosso otimizador é 1,53–1,68× MAIS RÁPIDO que o do framework** — a crítica
|
| 134 |
+
estava invertida. (E o nosso inclui telemetria de norma por passo.)
|
| 135 |
+
|
| 136 |
+
### 2.5 Por que o manual é estruturalmente necessário (não é teimosia)
|
| 137 |
+
|
| 138 |
+
`Optimizer::step(&GradStore)` do candle-nn consome o gradiente OPACO — não
|
| 139 |
+
há ponto de interceptação para: (a) **PCGrad** multi-perda (projeção por
|
| 140 |
+
pares ANTES do passo — Teo D.7, v070-3); (b) **clip de norma global** com
|
| 141 |
+
guarda inf/NaN (provado necessário na v0.5.0: `inf·escala = NaN` na IEEE
|
| 142 |
+
diverge o treino); (c) LR dinâmico auditado pelo controlador. Migrar para o
|
| 143 |
+
AdamW do framework **removeria** PCGrad e o clip — violaria o mandato de não
|
| 144 |
+
remover SOTA. O caminho correto era o inverso: manter o manual e **usar o do
|
| 145 |
+
framework como oráculo** (feito, §2.3).
|
| 146 |
|
| 147 |
---
|
| 148 |
|
| 149 |
+
## §3 — Crítica 3: serialização PyO3
|
| 150 |
+
|
| 151 |
+
### 3.1 Verificação factual
|
| 152 |
+
|
| 153 |
+
1. **O crate `numpy` (rust-numpy 0.23) já era dependência declarada no
|
| 154 |
+
Cargo.toml desde a v0.6 — e NUNCA foi usado** (verificado por grep:
|
| 155 |
+
zero ocorrências de `numpy::`). A crítica acertou o sintoma sem ver o
|
| 156 |
+
`Cargo.toml`.
|
| 157 |
+
2. A fronteira atual: **entrada** `Vec<Vec<u32>>` (lista-de-listas — pyo3
|
| 158 |
+
extrai 1 PyLong por token); **saída** `pythonize` de dicts pequenos de
|
| 159 |
+
telemetria; **callback** a cada 25 passos (dict pequeno). Os tensores
|
| 160 |
+
GRANDES nunca cruzam a fronteira (o treino vive todo em Rust).
|
| 161 |
+
|
| 162 |
+
### 3.2 Medição isolada da fronteira (método v090-7)
|
| 163 |
+
|
| 164 |
+
Funções de diagnóstico `_fronteira_lista` / `_fronteira_np` (recebem o dado
|
| 165 |
+
e devolvem o total — o custo da chamada É a conversão), 524.288 tokens:
|
| 166 |
+
|
| 167 |
+
| Caminho | tempo | razão |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
|---|---|---|
|
| 169 |
+
| lista-de-listas → `Vec<Vec<u32>>` | 5,83 ms | 2008× |
|
| 170 |
+
| array numpy → leitura de linhas | **0,0029 ms** | 1× |
|
| 171 |
+
|
| 172 |
+
**A crítica é VÁLIDA no ponto certo**: a conversão por elemento é ~2000× mais
|
| 173 |
+
cara que a leitura contígua. Porém o **impacto no pipeline** é limitado pela
|
| 174 |
+
Amdahl: os dados cruzam 1× por execução de treino (5,83 ms num treino de
|
| 175 |
+
minutos ≈ 0,05%) — a melhoria é de engenharia correta, não de throughput.
|
| 176 |
+
|
| 177 |
+
### 3.3 Implementação (aditiva — nenhum chamador quebra)
|
| 178 |
+
|
| 179 |
+
- `treinar_fase_a_np` / `treinar_fase_b_np`: entrada `PyReadonlyArray2<u32>`
|
| 180 |
+
(uma cópia plana, sem boxing por token); as funções de lista permanecem.
|
| 181 |
+
- `codificar_latentes`: extração de latentes REAIS do tokenizador treinado
|
| 182 |
+
→ numpy `(n, ctx, d_lat)`, **em lotes de 16** — a medição de memória
|
| 183 |
+
(§3.4) mostrou que o forward com lote grande morre por OOM.
|
| 184 |
+
- Gate `c3_numpy_nao_pior` (1/1) + pipeline real 71 exercita `_np` fim-a-fim.
|
| 185 |
|
| 186 |
+
### 3.4 Achado de engenharia (memória — lição do ambiente)
|
| 187 |
+
|
| 188 |
+
`codificar_latentes` com 256 janelas morria **em silêncio** (SIGKILL do OOM):
|
| 189 |
+
o checkpoint de 240 MB (JSON) cria pico de ~1,3 GB no parse, e o forward de
|
| 190 |
+
256×256 excede os 4 GB do ambiente. Correção: lotes de 16 (~200 MB de
|
| 191 |
+
ativações). Isso reconfirma o aviso do usuário: processos podem morrer SEM
|
| 192 |
+
erro visível — não é bug do código, é orçamento de memória (documentado).
|
| 193 |
|
| 194 |
---
|
| 195 |
|
| 196 |
+
## §4 — Crítica 4: alinhamento SIMD (e o plano de ação itens 1/3)
|
| 197 |
+
|
| 198 |
+
### 4.1 ERRATA-1: `RUSTFLAGS` não vai no `Cargo.toml`
|
| 199 |
+
|
| 200 |
+
`RUSTFLAGS="-C target-cpu=native -C target-feature=+avx512f,..."` **não pode
|
| 201 |
+
ser declarado em `[profile.release]`** (Cargo não suporta; só `.cargo/
|
| 202 |
+
config.toml` ou variável de ambiente). E num repositório **publicado** (nosso
|
| 203 |
+
caso — PowerMachine/khtst-rust), `target-cpu=native`/`+avx512f` como default
|
| 204 |
+
produz binários que travam com **SIGILL** em toda CPU sem AVX-512. A forma
|
| 205 |
+
correta — a que o próprio candle/gemm usa — é **despacho em runtime**:
|
| 206 |
+
`is_x86_feature_detected!` + kernel `#[target_feature]` + fallback escalar.
|
| 207 |
+
|
| 208 |
+
**Implementado** (`src/simd.rs`): `tem_avx512()` + kernels
|
| 209 |
+
`passo_adamw`/`escalar_slice`/`distancias4_*` com despacho — portabilidade
|
| 210 |
+
preservada, velocidade local aproveitada (CPU atual: Xeon com AVX-512 F/DQ/
|
| 211 |
+
BW/VL + BF16 + AMX, verificado via `/proc/cpuinfo`).
|
| 212 |
+
|
| 213 |
+
### 4.2 ERRATA-2: `#[repr(align(64))]` em struct com `Vec` não alinha o heap
|
| 214 |
|
| 215 |
+
```rust
|
| 216 |
+
#[repr(C, align(64))]
|
| 217 |
+
pub struct ArvorePesosSimd { pesos: Vec<f64>, ... } // proposta Qwen
|
| 218 |
+
```
|
| 219 |
|
| 220 |
+
Isso alinha a **STRUCT na pilha** (3 ponteiros) — o **buffer de heap do
|
| 221 |
+
`Vec`** continua com o alinhamento do alocador global (glibc: 16 B). Prova:
|
| 222 |
+
o layout de `Vec` é `(ptr, cap, len)` — o atributo atua no composite, não na
|
| 223 |
+
alocação apontada. **Implementado o correto**: `BufAlinhado64` com
|
| 224 |
+
`std::alloc::alloc(Layout::from_size_align(n, 64))` + `Drop` — verificação
|
| 225 |
+
por endereço no teste `simd_enderecos` (todas as alocações ≡ 0 mod 64).
|
|
|
|
| 226 |
|
| 227 |
+
### 4.3 Medição: o alinhamento não vale nada neste workload
|
| 228 |
+
|
| 229 |
+
axpy f64 de 4 M elementos (21 repetições, mediana):
|
| 230 |
+
|
| 231 |
+
| Buffer | tempo |
|
| 232 |
+
|---|---|
|
| 233 |
+
| alinhado 64 B (`BufAlinhado64`) | 1,45–1,55 ms |
|
| 234 |
+
| `Vec` comum (malloc 16 B) | 1,49–1,66 ms |
|
| 235 |
+
| desalinhado +8 B proposital | 1,45–1,58 ms |
|
| 236 |
+
|
| 237 |
+
Diferença **dentro do ruído (±5%)** — a alegação de "cache-line splits com
|
| 238 |
+
penalidades severas" é **refutada por medição** neste Xeon: o hardware
|
| 239 |
+
lida bem com cargas não alinhadas em streaming (a penalidade clássica
|
| 240 |
+
aplica-se a cargas alinhadas-forçadas dividindo linhas em loops com
|
| 241 |
+
dependências, não a streaming passivo). O workload é limitado por **largura
|
| 242 |
+
de banda de memória**, não por alinhamento.
|
| 243 |
+
|
| 244 |
+
### 4.4 Teo X-1 (bit-identidade dos kernels) ∎
|
| 245 |
+
|
| 246 |
+
**Afirmação**: os kernels `passo_adamw`, `escalar_slice`, `distancias4_exatas`
|
| 247 |
+
produzem saída bit-a-bit igual aos loops escalares que substituem.
|
| 248 |
+
|
| 249 |
+
**Prova**: cada kernel usa apenas `add/mul/sub/div/sqrt` — **sem FMA** (que
|
| 250 |
+
funde o arredondamento), sem `rsqrt`/`rcp` aproximados — e preserva a ordem
|
| 251 |
+
de avaliação elemento-a-elemento do escalar, incluindo o detalhe
|
| 252 |
+
`(1.0f32 − (β as f32))` (subtração em f32 pós-cast, réplica exata do código
|
| 253 |
+
v0.8.0). IEEE-754: mesma sequência de operações round-to-nearest ⇒ mesmo
|
| 254 |
+
resultado em cada bit. Reduções (somas) permanecem escalares ou seguem a
|
| 255 |
+
ordem exata do fold — `norma_global` fica escalar por design. **Testes de
|
| 256 |
+
falsificação**: `adamw_bit_a_bit`, `escalar_bit_a_bit`, `distancias_bit_a_bit`,
|
| 257 |
+
`distancias_bit_a_bit_rm` comparam `to_bits()` — e o bench confirma
|
| 258 |
+
`adamw_bit_identico: true` em cena de 2 M elementos. ∎
|
| 259 |
+
|
| 260 |
+
### 4.5 Medição honesta do ganho SIMD (AdamW 2 M elementos)
|
| 261 |
+
|
| 262 |
+
| Caminho | tempo | razão |
|
| 263 |
+
|---|---|---|
|
| 264 |
+
| escalar (SSE2 baseline) | 1,92–2,06 ms | 1× |
|
| 265 |
+
| SIMD AVX-512 (16 lanes) | 1,73–1,91 ms | **1,03–1,11×** |
|
| 266 |
|
| 267 |
+
Por que tão pouco? **Roofline**: o passo AdamW toca 5 arrays (w, g, m, v,
|
| 268 |
+
out) × 2 M × 4 B = **40 MB de tráfego** por passo; a ~30 GB/s efetivos, o
|
| 269 |
+
piso de memória é ~1,3 ms — os dois caminhos estão grudados no teto de
|
| 270 |
+
banda. O compute (sqrt/div) esconde-se sob as cargas. **Conclusão**: a
|
| 271 |
+
esperança implícita de "grande ganho AVX-512 no otimizador" não se
|
| 272 |
+
materializa em CPUs com este perfil de memória — o ganho real do kernel é
|
| 273 |
+
nos casos compute-bound (que este passo não é). Reportado honestamente.
|
| 274 |
|
| 275 |
+
---
|
|
|
|
| 276 |
|
| 277 |
+
## §5 — Proposta A: árvore binária implícita (heap layout) / Fenwick
|
| 278 |
+
|
| 279 |
+
**ERRATA conceitual (3 partes)**:
|
| 280 |
+
|
| 281 |
+
1. A `ArvorePesos` **não é uma árvore binária de busca** — é uma árvore
|
| 282 |
+
enraizada de MÓDULOS (12 nós, aridade arbitrária, arestas duplas com
|
| 283 |
+
`id_par`, crédito/regulação por enlace — T-A1..T-A6 da v0.6.0). O layout
|
| 284 |
+
implícito `filhos de i em 2i+1/2i+2` **só se define para árvores binárias
|
| 285 |
+
completas** — não se aplica.
|
| 286 |
+
2. A Fenwick Tree resolve **somas de prefixo em arrays** (índices 1..n com
|
| 287 |
+
operação comutativa) — a nossa agregação é fundo-topo com pesos λ^k por
|
| 288 |
+
PROFUNDIDADE sobre um grafo de módulos com métricas por enlace. Abstração
|
| 289 |
+
diferente; substituir seria remover a semântica de regulação por nível.
|
| 290 |
+
3. "Pointer chasing paralisa o pipeline": com **12 nós** (~100 B cada), a
|
| 291 |
+
árvore inteira cabe em ~2 KB — residente em L1 (48 KB). A agregação
|
| 292 |
+
completa mede **µs** num pipeline cujo passo é ~200 ms.
|
| 293 |
+
|
| 294 |
+
**Amdahl formal**: mesmo que a agregação ficasse 100× mais rápida, o ganho
|
| 295 |
+
no pipeline seria `t_agg/(100·t_passo)` ≈ 1e-6 — invisível. E a v0.8.0 JÁ
|
| 296 |
+
tornou a travessia iterativa com pilha explícita (T-A7/T-A7b: 100k nós sem
|
| 297 |
+
estourar pilha, sem clones). **Veredito: NÃO APLICÁVEL** — rejeitada com
|
| 298 |
+
prova, sem churn (Navalha de Ockham). Se a árvore crescer para 10⁵+ nós de
|
| 299 |
+
módulos, o layout contíguo merece reavaliação — registrado como condição
|
| 300 |
+
explícita, não como débito.
|
| 301 |
|
| 302 |
+
---
|
| 303 |
|
| 304 |
+
## §6 — Proposta B: SOM (crescimento por entropia, Lyapunov, SoA)
|
| 305 |
+
|
| 306 |
+
### 6.1 B.2 ERRATA teórica (Lyapunov/global — a alegação mais forte é a errada)
|
| 307 |
+
|
| 308 |
+
A proposta afirma: Robbins-Monro (Ση=∞, Ση²<∞) faz o SOM "convergir para um
|
| 309 |
+
**mínimo global** onde a topologia reflete **isometricamente** a variedade".
|
| 310 |
+
|
| 311 |
+
**O que é verdade**: as condições de Robbins-Monro dão convergência da
|
| 312 |
+
aproximação estocástica aos **pontos estacionários do campo médio** (teoria
|
| 313 |
+
de Benaim–Hirsch para ODEs limites) — não a mínimos globais.
|
| 314 |
+
|
| 315 |
+
**O que é falso/aberto**: a convergência global do SOM é **problema aberto
|
| 316 |
+
para dimensão ≥ 2** — provada apenas para cadeias 1-D (Cottrell–Fort) e
|
| 317 |
+
casos especiais; existem **contraexemplos** de preservação topológica em
|
| 318 |
+
topologias inadequadas; a "isometria" exige hipóteses fortes (densidade
|
| 319 |
+
uniforme da amostra, grade compatível — Fort–Pagès) que dados reais violam.
|
| 320 |
+
|
| 321 |
+
**O que já tínhamos provado** (e segue de pé): S4-1 (QE não-crescente no
|
| 322 |
+
modo lote determinístico, v0.5.0). A energia `V = E_quant + λH_topo` da
|
| 323 |
+
proposta é um **candidato de função de Lyapunov LOCAL** (decresce nos
|
| 324 |
+
equilíbrios sob as condições de escala) — não um certificado global. A
|
| 325 |
+
afirmação correta é: *sob Robbins-Monro, o sistema converge a.s. a um
|
| 326 |
+
conjunto invariante do campo médio; a regularização por entropia desloca os
|
| 327 |
+
equilíbrios ao penalizar aglomeração*. Nenhuma garantia de mínimo global.
|
| 328 |
+
|
| 329 |
+
### 6.2 B.1 T-G1 (portão de entropia — piso de alívio) ∎
|
| 330 |
+
|
| 331 |
+
**Definição**: `H(e)/ln V` com `p_i = e_i/Σe` (entropia de Shannon do vetor
|
| 332 |
+
de erro, normalizada; Σe=0 ⇒ H:=1).
|
| 333 |
+
|
| 334 |
+
**Teorema T-G1**: `e_max ≥ Σe · V^(−H/ln V)`. *Prova*: seja `p* = e_max/Σe`.
|
| 335 |
+
Como `−ln p_j` ponderado por p_j define H: `H = Σ p_j ln(1/p_j) ≥ p* ln(1/p*)`
|
| 336 |
+
(todos os termos são ≥ 0). Logo `H/ln V ≥ log_V(1/p*)`, i.e.
|
| 337 |
+
`p* ≥ V^(−H/ln V)`. Com o portão `H/ln V ≤ teto`: `e_max ≥ Σe·V^(−teto)` —
|
| 338 |
+
a inserção no pico tem alívio mínimo GARANTIDO. Sem o portão (erro
|
| 339 |
+
uniforme): `e_max = Σe/V` apenas. ∎
|
| 340 |
+
|
| 341 |
+
### 6.3 MEDIÇÃO que refutou minha primeira versão (autocrítica v090-6)
|
| 342 |
+
|
| 343 |
+
`EntropiaConcentrada { teto }` como **gatilho único** sofre **FOME** em
|
| 344 |
+
dados multimodais simétricos (2 clusters gaussianos iguais): os dois
|
| 345 |
+
vértices acumulam erro igual ⇒ H/ln V = 1 sempre ⇒ **0 inserções** (EQ 0,976
|
| 346 |
+
vs 0,448 do padrão, 60 bloqueadas). A lição teórica: **a entropia diz ONDE
|
| 347 |
+
há concentração; o NÍVEL absoluto (QE) diz SE há necessidade** — nenhum
|
| 348 |
+
estatístico de concentração distingue "dois clusters iguais precisando de
|
| 349 |
+
refinamento" de "quantização completa". Correção: **`EntropiaComPiso { teto,
|
| 350 |
+
frac_qe }`** — insere se `H ≤ teto` OU `QE_atual > frac_qe·QE_inicial`
|
| 351 |
+
(T-G1': a disjunção preserva o piso de T-G1 no ramo de entropia e adiciona
|
| 352 |
+
o piso absoluto no ramo de nível).
|
| 353 |
+
|
| 354 |
+
### 6.4 MEDIÇÃO em dados reais (latentes d=64 do tokenizador treinado na
|
| 355 |
+
Wikipédia PT-BR; nuvem de 2000 pontos; orçamento 48 vértices)
|
| 356 |
+
|
| 357 |
+
| Critério | vértices | EQ | inserções |
|
| 358 |
+
|---|---|---|---|
|
| 359 |
+
| erro máximo (padrão) | 48 | 0,184 | 46 |
|
| 360 |
+
| entropia+piso (0,85/0,5) | **33** | 0,211 | 31 (+49 bloqueadas) |
|
| 361 |
+
|
| 362 |
+
**Leitura honesta**: a orçamento IGUAL de vértices, o padrão vence (a
|
| 363 |
+
proposta não entrega o que insinua); o que o portão entrega é **regulação de
|
| 364 |
+
orçamento** — para de crescer com 31% menos vértices ao custo de 14,5% de EQ
|
| 365 |
+
quando o erro está difuso e o QE já caiu pela metade. É um mecanismo de
|
| 366 |
+
**stop-learning** (a mesma filosofia do S-AB da v0.7.0), não um melhorador
|
| 367 |
+
de qualidade.
|
| 368 |
+
|
| 369 |
+
### 6.5 B.3 SoA/AVX-512 "~8×": a paisagem completa medida
|
| 370 |
+
|
| 371 |
+
4 implementações da busca BMU (c=4096 protótipos, d=4, 2000 consultas,
|
| 372 |
+
medição mediana):
|
| 373 |
+
|
| 374 |
+
| Implementação | tempo | vs escalar |
|
| 375 |
+
|---|---|---|
|
| 376 |
+
| **escalar `Vec<Vec<f64>>` (v0.8.0)** | **15,3 ms** | **1×** |
|
| 377 |
+
| SoA coluna-major + AVX-512 exato | 18,6–19,3 ms | 0,80× |
|
| 378 |
+
| row-major plano + AVX-512 (2 protót./ZMM) | 21,7–22,4 ms | 0,69× |
|
| 379 |
+
| matmul gemm (`‖q‖²−2qw+‖w‖²`) | 28,5–29,3 ms | 0,52× |
|
| 380 |
+
|
| 381 |
+
**TODAS as variantes vetorizadas são mais lentas que o loop escalar
|
| 382 |
+
original.** Prova de por quê (lógica computacional):
|
| 383 |
+
|
| 384 |
+
1. **A busca argmin não materializa distâncias** — o escalar mantém o
|
| 385 |
+
mínimo corrente com O(1) memória; toda variante que gera o array `c`
|
| 386 |
+
paga c×8 B de ESCRITA que o escalar não paga;
|
| 387 |
+
2. **d=4 ⇒ a redução por protótipo é 3 somas dependentes** (cadeia de
|
| 388 |
+
latência ~9 ciclos) — nos kernels, a redução horizontal domina o ganho
|
| 389 |
+
das 4 sub/mul vetorizadas; no escalar, o ILP ENTRE protótipos já
|
| 390 |
+
preenche o pipeline;
|
| 391 |
+
3. **os pesos (128 KB) são L2-residentes e reutilizados** entre consultas —
|
| 392 |
+
o gargalo é o fluxo de leitura dos pesos, não o compute; vetorizar o
|
| 393 |
+
compute não alivia memória;
|
| 394 |
+
4. **sem FMA não há compute a colher** — e FMA quebraria a bit-identidade
|
| 395 |
+
(X-1) por um ganho que os itens 1–3 já limitam.
|
| 396 |
+
|
| 397 |
+
A alegação "~8× com FMA" é **refutada por medição com 4 variantes**. Onde a
|
| 398 |
+
vetorização VENCE: na geração do **array completo** de distâncias (necessário
|
| 399 |
+
em `metricas`, que precisa dos DOIS melhores para TE) — a v0.8.0 fazia 2
|
| 400 |
+
varreduras completas por amostra (bmu + segundo-loop); a v0.9.0 faz **1
|
| 401 |
+
varredura** SoA + `dois_menores` (bit-a-bit idêntica, Teo X-2). A atribuição
|
| 402 |
+
de época (argmin-only) **mantém o escalar — agora como ótimo MEDIDO**, não
|
| 403 |
+
como default por inércia.
|
| 404 |
|
| 405 |
+
---
|
| 406 |
|
| 407 |
+
## §7 — Proposta C: Thompson Sampling (MoE) + Information Bottleneck (JEPA)
|
| 408 |
+
|
| 409 |
+
### 7.1 Reubicação da Thompson para MoE (análise deonde ela pertence)
|
| 410 |
+
|
| 411 |
+
A proposta aplica TS ao **roteamento por token**. Problemas: (a) nosso
|
| 412 |
+
roteamento é determinístico (M-R2/S4-2 — gates de bit-identidade e
|
| 413 |
+
reprodutibilidade); (b) o roteador é treinado POR GRADIENTE — a exploração
|
| 414 |
+
já vem do ruído do treino; TS por token adicionaria variância sem sinal de
|
| 415 |
+
recompensa por token. **O lugar certo do TS é a camada de SELEÇÃO** (onde a
|
| 416 |
+
v0.6.0 já usa UCB1 no loop iterativo de autoajuste — script 58): lá a
|
| 417 |
+
recompensa é bem definida (queda de perda por configuração), a decisão é
|
| 418 |
+
por-época e o determinismo é preservável com RNG semeado.
|
| 419 |
+
|
| 420 |
+
### 7.2 T-TS0 (amostragem Gama/Beta) ∎
|
| 421 |
+
|
| 422 |
+
`RngDeterministico::gamma(a)`: Marsaglia–Tsang para a ≥ 1 (aceitação squeeze
|
| 423 |
+
`u < 1−0,0331z⁴` ou log; a razão de aceitação é a razão densidade-alvo/
|
| 424 |
+
proposta — rejeição clássica) + reforço `G(a) = G(a+1)·U^{1/a}` para a < 1.
|
| 425 |
+
`beta(α,β) = X/(X+Y)` com gamas independentes. **Teste de momentos**
|
| 426 |
+
(`teste_gamma_beta`): média≈a, var≈a para a ∈ {0,5; 1; 2,5; 8} (n=200k,
|
| 427 |
+
tolerância 5–8%); Beta(2,5): média≈2/7; determinismo bit-a-bit. ∎
|
| 428 |
+
|
| 429 |
+
### 7.3 T-TS1 (posterior, concentração, arrependimento) ∎
|
| 430 |
+
|
| 431 |
+
1. **Conjugação**: prior Beta(1,1) + Bernoulli ⇒ posterior Beta(1+s, 1+f)
|
| 432 |
+
exata (fato clássico);
|
| 433 |
+
2. **Concentração**: Var[Beta(α,β)] = αβ/((α+β)²(α+β+1)) ~ 1/(α+β) — o
|
| 434 |
+
posterior encolhe como O(√(ln t/t)) nos braços jogados;
|
| 435 |
+
3. **Arrependimento assintótico instância-ótimo** para Beta-Bernoulli TS
|
| 436 |
+
(Agrawal–Goyal 2012, Thm 1): E[R_T] = O(Σ_a Δ_a ln T/kl_a) — matching
|
| 437 |
+
com o limite de Lai–Robbins; o UCB1 (Auer) tem a cota finita-tempo limpa
|
| 438 |
+
Σ 8lnT/Δ + (1+π²/3) — **os dois coexistem** (o UCB1 NÃO foi removido);
|
| 439 |
+
4. **Recompensas contínuas** r ∈ [0,1] via Bernoulli-ização (u < r ⇒
|
| 440 |
+
sucesso) — preserva as garantias (Agrawal–Goyal §1.2). ∎
|
| 441 |
+
|
| 442 |
+
### 7.4 T-TS2 + medição (sintético 3 braços, 4000 passos)
|
| 443 |
+
|
| 444 |
+
Determinismo: RNG semeado ⇒ mesma trajetória bit-a-bit (teste
|
| 445 |
+
`ts_deterministico`). Regret médio: **Thompson 0,0019 / UCB1 0,0156 /
|
| 446 |
+
uniforme 0,2999** — TS 8× melhor que UCB1 nesta instância (constantes
|
| 447 |
+
empíricas melhores, como a literatura prevê); ambos aprendem (gates 3/3).
|
| 448 |
+
Posterior aproxima as probabilidades verdadeiras (±0,15).
|
| 449 |
+
|
| 450 |
+
### 7.5 T-J1 (conexão Information Bottleneck — formalização honesta) ∎
|
| 451 |
+
|
| 452 |
+
**Afirmação**: minimizar a perda preditiva JEPA `E‖s(y) − f(s(x_ctx))‖²`
|
| 453 |
+
maximiza um **limite inferior** de `I(Z; Y | X_ctx)`; a fração de máscara f
|
| 454 |
+
controla um trade-off em U — f→0 dá atalho de identidade (informação
|
| 455 |
+
trivial), f→1 destrói o contexto (erro de estimação domina).
|
| 456 |
+
|
| 457 |
+
**Prova (esboço)**: sob ruído gaussiano homocedástico ε ~ N(0, σ²I), o MSE
|
| 458 |
+
ótimo é `E‖s(y) − E[s(y)|x_ctx]‖² + σ²·dim` — o resíduo epistêmico; a
|
| 459 |
+
relação Gaussiana `I = ½·log(1+SNR)` liga minimizar MSE a maximizar a
|
| 460 |
+
informeração mútua na família exponencial Gaussiana (princípio InfoMax;
|
| 461 |
+
Latham–Brunel para o caso de população). Nenhum f fixo é ótimo
|
| 462 |
+
universalmente — o ótimo depende dos dados (daí a adaptação). O QUE A
|
| 463 |
+
PROPOSTA NÃO PROVA (e nós não reclamamos): a estimativa de I(z; x_ctx) do
|
| 464 |
+
IB exato é intratável em geral — o esquema prático usa a perda preditiva
|
| 465 |
+
como surrogate, o que é padrão na família InfoNCE/InfoMax. ∎
|
| 466 |
+
|
| 467 |
+
### 7.6 T-J2 (esquema implementado + recompensa honesta)
|
| 468 |
+
|
| 469 |
+
Bandit Thompson sobre `f ∈ {0,2; 0,35; 0,5; 0,65}` escolhe a fração por
|
| 470 |
+
época; recompensa = **melhoria relativa da perda PREDITIVA ISOLADA** do
|
| 471 |
+
próprio braço: `r = ½ + (l_prev − l)/(2·l_prev)` ∈ [0,1] — ½ =
|
| 472 |
+
estagnação; oscilações simétricas têm média ½ (não enganam o posterior em
|
| 473 |
+
expectativa). **O RNG do bandit é um fluxo SEPARADO** (semente derivada por
|
| 474 |
+
XOR) — o fluxo de máscara permanece intocado: com 1 braço, o treino
|
| 475 |
+
adaptativo é **bit-a-bit idêntico** ao `treinar_variante` fixo (teste
|
| 476 |
+
`jepa_adaptativa_um_braco_e_fixa` — falsifica qualquer vazamento).
|
| 477 |
+
|
| 478 |
+
### 7.7 Medição em dados reais (JEPA-T sobre latentes REAIS d=64)
|
| 479 |
+
|
| 480 |
+
4 janelas de 256 latentes do tokenizador treinado (Wikipédia PT-BR), 40
|
| 481 |
+
épocas cada, mesma semente:
|
| 482 |
+
|
| 483 |
+
| Esquema | queda média da perda preditiva |
|
| 484 |
+
|---|---|
|
| 485 |
+
| fração fixa 0,35 | 49,1% |
|
| 486 |
+
| **adaptativa (4 braços)** | **50,0%** |
|
| 487 |
+
|
| 488 |
+
Todos os 4 braços jogados; sem colapso (std_emb > 0,05). **Leitura
|
| 489 |
+
honesta**: ganho marginal (+0,9 pp) — em latentes JÁ pré-treinados a curva
|
| 490 |
+
U é rasa neste orçamento; o valor do mecanismo é (a) não degradar,
|
| 491 |
+
(b) auto-selecionar sem grid search, (c) trilha auditável
|
| 492 |
+
(`jogadas_por_braco` + `posterior`). A afirmação forte (adaptativo ≫ fixo)
|
| 493 |
+
exigiria dados brutos (não pré-treinados) e mais compute — débito §12.
|
| 494 |
+
|
| 495 |
+
### 7.8 Gumbel-Softmax (o que ficou como extensão formalizada)
|
| 496 |
+
|
| 497 |
+
Máscara diferenciável por posição via Concrete/Gumbel-Sigmoid:
|
| 498 |
+
`m_i = σ((g_i + logit π_i)/τ)` com straight-through para o forward binário e
|
| 499 |
+
gradiente do relaxamento. Por que não agora: (a) o núcleo JEPA trata a
|
| 500 |
+
máscara como índices booleanos — a mudura exige reescrever a montagem de
|
| 501 |
+
alvos com pesos suaves (blast radius no mecanismo provado J-1..J-7);
|
| 502 |
+
(b) candle 0.11 não expõe `stop_grad` de tensor intermediário — o
|
| 503 |
+
straight-through precisa do truque identidade `m_duro − m_suave.detach() +
|
| 504 |
+
m_suave`, cujo `.detach()` em meio de grafo rompe a nossa convenção de
|
| 505 |
+
rastreabilidade. O estimador está formalizado aqui para a extensão.
|
| 506 |
|
| 507 |
---
|
| 508 |
|
| 509 |
+
## §8 — Proposta D: monomorfização, DAG com fusão, precisão dinâmica
|
| 510 |
+
|
| 511 |
+
### 8.1 D1 (dyn → genéricos): auditoria + Amdahl
|
| 512 |
+
|
| 513 |
+
Varredura completa: **6 sítios** `&dyn` no projeto — `ponte.rs:154`,
|
| 514 |
+
`jepa.rs:653`, `pensamento.rs:241/527/765`, `fnh.rs:689` — todos são
|
| 515 |
+
**closures passadas por argumento, chamadas 1× por LOTE/ÉPOCA** (nunca
|
| 516 |
+
por elemento). Custo do dispatch: ~5–10 ns × ~2 chamadas/passo de treino
|
| 517 |
+
(~200 ms) ⇒ **< 1e-7 relativo**. Monomorfizar exigiria converter APIs
|
| 518 |
+
públicas em genéricas (churn em 5 módulos) por ganho **abaixo do ruído de
|
| 519 |
+
cronômetro**. **Veredito: rejeitado por Amdahl + Ockham** — com a ressalva
|
| 520 |
+
honesta: se um `dyn` entrasse em laço por elemento, a crítica valeria.
|
| 521 |
+
|
| 522 |
+
### 8.2 D2 (DAG em tempo de compilação com fusão de operadores)
|
| 523 |
+
|
| 524 |
+
1. **candle é eager** — não existe IR de grafo, não existe fusor; "construir
|
| 525 |
+
um DAG via macros procedurais" é projetar um XLA para um framework que
|
| 526 |
+
deliberadamente não tem um — fora de escopo (e contradiz a razão pela
|
| 527 |
+
qual escolhemos candle: simplicidade auditável);
|
| 528 |
+
2. **ERRATA técnica**: "o tensor intermediário T_A nunca é escrito na RAM,
|
| 529 |
+
permanece nos **registradores ZMM**" — impossível além de 32×8 = 256
|
| 530 |
+
doubles (spilling imediato); o benefício real da fusão é **residência em
|
| 531 |
+
L1/L2**, não registradores;
|
| 532 |
+
3. **A fusão que existia para existir já existe**: os nossos loops internos
|
| 533 |
+
são de passada única (AdamW: m+v+θ numa varredura; clip: uma varredura;
|
| 534 |
+
telescópio polar Newton-Schulz: bloco único). Os pontos onde a fusão
|
| 535 |
+
ajudaria (cadeias elementwise DENTRO do candle) são do domínio do crate,
|
| 536 |
+
não nosso. **Veredito: refutado como proposto**; a parte aplicável
|
| 537 |
+
(loops próprios fundidos) já estava feita — agora documentada.
|
| 538 |
+
|
| 539 |
+
### 8.3 D3 (precisão dinâmica f32→bf16/int8 por variância de gradiente)
|
| 540 |
+
|
| 541 |
+
1. **ERRATA dimensional**: `ΔE ≤ ½·L·δ²` com "L constante de Lipschitz" —
|
| 542 |
+
para perturbação de PESOS por quantização com passo δ, o pior caso é
|
| 543 |
+
`ΔE ≤ L·δ/2` (primeira ordem); o termo `½·δ²` é de SEGUNDA ordem e
|
| 544 |
+
exige a constante de Lipschitz do GRADIENTE (HessianA), não da função.
|
| 545 |
+
2. **A forma correta (Teo T-D3)** ∎: preservação de direção —
|
| 546 |
+
`cos(g, g+ε) ≥ 1 − ‖ε‖₂/‖g‖₂` por Cauchy–Schwarz
|
| 547 |
+
(`|⟨g,ε⟩| ≤ ‖g‖‖ε‖`). Logo: quantização é direção-segura quando a razão
|
| 548 |
+
sinal-ruído `‖g‖/‖ε‖` é alta — exatamente o regime "variância baixa"
|
| 549 |
+
que a proposta intui (com σ_g grande, ε relativo é pequeno). Este bound
|
| 550 |
+
agora BLINDA o `SuavizadorQuant` EMA+STE existente (F-8): o quantizado
|
| 551 |
+
preserva direção de descida sob passo proporcional à escala EMA. ∎
|
| 552 |
+
3. **MEDIÇÃO que refuta o throughput**: bf16 no backend CPU do candle é
|
| 553 |
+
**3× MAIS LENTO** que f32 (razão 0,32–0,34×; 8 M elementos, cadeia
|
| 554 |
+
add+affine+sqr): as ops bf16 fazem upcast por operação (software
|
| 555 |
+
`half::bf16`) — o `avx512_bf16`/AMX existem nesta CPU, mas NÃO são
|
| 556 |
+
alcançáveis pelas ops elementwise do crate sem kernels tile
|
| 557 |
+
customizados. A claim "16 ops/ciclo" não se materializa nesta stack.
|
| 558 |
+
**Veredito**: o mecanismo de troca dinâmica é rejeitado como speedup;
|
| 559 |
+
o THEOREMA de segurança de direção (T-D3) é adotado como garantia do
|
| 560 |
+
que já tínhamos.
|
| 561 |
|
| 562 |
---
|
| 563 |
|
| 564 |
+
## §9 — Plano de ação (5 itens): veredito por item
|
| 565 |
+
|
| 566 |
+
| # | Item proposto | Veredito | O que foi feito |
|
| 567 |
+
|---|---|---|---|
|
| 568 |
+
| 1 | `RUSTFLAGS` AVX-512 no `Cargo.toml` | **ERRATA + feito do jeito certo** | não cabe em `[profile]`; SIGILL em CPUs sem AVX-512 se global; implementado despacho em runtime (§4.1) |
|
| 569 |
+
| 2 | `#[derive(Deserialize)]` tipado | **já existia no config principal**; demos corrigidas | structs tipadas + erro explícito de tipo (§1) |
|
| 570 |
+
| 3 | `UnsafeCell` + `#[repr(align(64))]` | **ERRATA** (não alinha heap) | `BufAlinhado64` correto + medição mostrando ganho nulo em streaming (§4.2/4.3) |
|
| 571 |
+
| 4 | rust-numpy zero-copy | **VÁLIDO** (dependência já declarada, nunca usada) | caminhos `_np` + `codificar_latentes` + medição 2008× (§3) |
|
| 572 |
+
| 5 | `candle-nn::optimizers::AdamW` | **refutado** (path errado; sem PCGrad/clip; 1,53× mais lento) | mantido manual COM oráculo de equivalência contra o do framework (§2) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 573 |
|
| 574 |
---
|
| 575 |
|
| 576 |
+
## §10 — Bugs catalogados nesta iteração (autocrítica, tradição r-07/r-27/r-33)
|
| 577 |
|
| 578 |
| ID | Onde | Causa-raiz | Correção |
|
| 579 |
|----|------|-----------|----------|
|
| 580 |
+
| v090-1 (meu) | jepa.rs `RelatorioJepaAdaptiva` | inconsistência de grafia entre a DEFINIÇÃO (`Adaptiva`) e os USOS (`Adaptativa`) — o rustc apontava "similarly named" e eu caçei caracteres invisíveis inexistentes por vários passos antes de LER o hint literalmente | padronizado `Adaptativa`; lição: ler a sugestão do compilador ANTES de teorizar |
|
| 581 |
+
| v090-2 (meu) | bench A/B | o "bit-check" comparava estados `m/v` EVOLUVIDOS pelo loop de timing (maçãs com laranças) ⇒ falso-negativo de bit-identidade | estados frescos iguais para os dois caminhos |
|
| 582 |
+
| v090-3 (meu) | kernel `distancias4_rm_avx` | `par` JÁ continha os quadrados e o código quadrava DE NOVO (`par[0]*par[0]`) | capturado pelo PRÓPRIO teste de bit-identidade antes de qualquer uso — o processo analisar→provar→testar funcionou |
|
| 583 |
+
| v090-4 (meu) | `codificar_latentes` | tupla de dims com aridade errada + código morto de remendos sucessivos | limpeza; `total` capturado antes do move |
|
| 584 |
+
| v090-5 (meu) | `codificar_latentes` 256 janelas | OOM silencioso (pico 1,3 GB do parse do checkpoint + forward grande > 4 GB do ambiente) — o modo de morte que o usuário avisou | lotes de 16 (~200 MB) + documentação |
|
| 585 |
+
| v090-6 (meu, TEÓRICO) | `EntropiaConcentrada` pura | formulei o portão de entropia sem o piso de nível — FOME em multimodais simétricos (0 inserções, EQ 0,98 vs 0,45) | `EntropiaComPiso` (disjunção nível-OU-concentração, T-G1'); a falha sintética virou evidência no doc |
|
| 586 |
+
| v090-7 (meu) | suite c3 (medição de fronteira) | método por diferença de treinos com teto por TEMPO media ruído (marginal negativa — sem sentido físico) | funções de diagnóstico isoladas `_fronteira_*` — o custo medido É a conversão |
|
| 587 |
+
|
| 588 |
+
O v090-3/v090-6 ilustram o valor do método: o primeiro foi capturado pelo
|
| 589 |
+
teste de falsificação antes de chegar a produção; o segundo é um erro de
|
| 590 |
+
**modelagem** que só a medição em dado adversário expôs — e virou teorema
|
| 591 |
+
corrigido (T-G1') em vez de bug silencioso.
|
| 592 |
+
|
| 593 |
+
---
|
| 594 |
+
|
| 595 |
+
## §11 — Resultados consolidados
|
| 596 |
+
|
| 597 |
+
1. **Cargo 111/111** (99 herdados + 12 novos: `adamw_bit_a_bit`,
|
| 598 |
+
`escalar_bit_a_bit`, `distancias_bit_a_bit`, `distancias_bit_a_bit_rm`,
|
| 599 |
+
`simd_enderecos`, `despacho_conta_chamadas`, `teste_gamma_beta`,
|
| 600 |
+
`ts_vs_ucb1_arrependimento`, `ts_deterministico`, `ts_recompensa_continua`,
|
| 601 |
+
`jepa_adaptativa_um_braco_e_fixa`, `adamw_oraculo_candle_nn`).
|
| 602 |
+
2. **Suite 70: 104/104 gates** (legado v0.8.0 85/85 + c1 2/2 + c2c4 5/5 +
|
| 603 |
+
c3 1/1 + b1 2/2 + thompson 3/3 + jepa 3/3 + cargo).
|
| 604 |
+
3. **Medições-chave** (Xeon AVX-512, mediana de 21/7 repetições):
|
| 605 |
+
AdamW nosso 1,53× mais rápido que candle-nn; SIMD AdamW 1,08× (memory-
|
| 606 |
+
bound, honesto); alinhamento 64 B: efeito nulo; bf16: 0,34× (3× mais
|
| 607 |
+
lento); fronteira numpy 2008×; BMU: escalar ótimo em d=4 (4 variantes
|
| 608 |
+
testadas); Thompson regret 0,0019 vs UCB1 0,0156 vs uniforme 0,2999.
|
| 609 |
+
4. **Dados reais** (Wikipédia PT-BR 273 artigos / 1121 janelas ctx 256):
|
| 610 |
+
fases A/B/C com entrada numpy (CE fase C 3,9221→3,9128 em 60 passos;
|
| 611 |
+
confiança 0,0213→0,0224 — mesma escala honesta da v0.7/v0.8); JEPA-T
|
| 612 |
+
real: adaptativa 50,0% vs fixa 49,1%; GNG real: entropia+piso = 33v/EQ
|
| 613 |
+
0,211 vs padrão 48v/EQ 0,184 (regulador de orçamento).
|
| 614 |
+
5. **Nada removido**: `fwd` MoE de referência, golden-section, polar, UCB1,
|
| 615 |
+
funções de lista, `NucleoJepa::novo`, caminhos escalares — todos
|
| 616 |
+
preservados; o que mudou de caminho interno é bit-a-bit idêntico (X-1/X-2).
|
| 617 |
+
|
| 618 |
+
## §12 — Débitos honestos remanescentes
|
| 619 |
+
|
| 620 |
+
1. A promessa de ganho SIMD no passo AdamW não se materializa (memory-bound)
|
| 621 |
+
— o kernel fica como infraestrutura correta para futuros casos
|
| 622 |
+
compute-bound (ex.: se o passo fundir mais ops por elemento).
|
| 623 |
+
2. JEPA adaptativa em dados BRUTOS (não latentes pré-treinados) + mais
|
| 624 |
+
épocas: é onde a curva U deve se aprofundar e a vantagem aparecer;
|
| 625 |
+
orçamento de compute atual não fecha a conta (herdado 10–50×).
|
| 626 |
+
3. Gumbel-Softmax (máscara diferenciável): formalizado (§7.8), não
|
| 627 |
+
implementado — blast radius no núcleo J-1..J-7 exige planejamento próprio.
|
| 628 |
+
4. Checkpoints em JSON: pico de 1,3 GB no parse (medido, §3.4) — migrar para
|
| 629 |
+
safetensors/binário aliviaria memória e tempo de carga; mudança de
|
| 630 |
+
formato exige migração dos checkpoints publicados.
|
| 631 |
+
5. A comparação TS vs UCB1 é empírica (1 instância sintética + JEPA real);
|
| 632 |
+
o pareamento formal de constantes em bandits com recompensas de treino
|
| 633 |
+
(não-estacionárias) é literatura aberta — nossos gates são direcionais.
|
| 634 |
+
6. A inicialização do SonicMoE/ReplaySSM/pensamento (cabeças de candidatos)
|
| 635 |
+
ainda usa o RNG global do candle — apenas o NÚCLEO JEPA foi semeado
|
| 636 |
+
nesta iteração (débito herdado r-33 §8.1 parcialmente pago).
|
pyproject.toml
CHANGED
|
@@ -4,12 +4,12 @@ build-backend = "maturin"
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "khtst-rust"
|
| 7 |
-
version = "0.
|
| 8 |
-
description = "KHTST_RUST —
|
| 9 |
requires-python = ">=3.10"
|
| 10 |
license = { text = "Apache-2.0" }
|
| 11 |
|
| 12 |
[tool.maturin]
|
| 13 |
python-source = "python"
|
| 14 |
module-name = "khtst_rust.nucleo_rust"
|
| 15 |
-
features = ["
|
|
|
|
| 4 |
|
| 5 |
[project]
|
| 6 |
name = "khtst-rust"
|
| 7 |
+
version = "0.9.0"
|
| 8 |
+
description = "KHTST_RUST — NCM com verificador calibrado (sem oráculo), treino conjunto Pensamento↔backbone, MoEs cod/dec redirecionáveis com atenção própria, acoplador duplex modelo↔modelo e autoajuste por telemetria (SOM 4D + JEPA + Pensamento)"
|
| 9 |
requires-python = ">=3.10"
|
| 10 |
license = { text = "Apache-2.0" }
|
| 11 |
|
| 12 |
[tool.maturin]
|
| 13 |
python-source = "python"
|
| 14 |
module-name = "khtst_rust.nucleo_rust"
|
| 15 |
+
features = ["pyo3/extension-module"]
|
resultados/suite_v090_final.json
CHANGED
|
@@ -2,12 +2,13 @@
|
|
| 2 |
"versao": "0.9.0",
|
| 3 |
"blocos": {
|
| 4 |
"legado_v080": "85/85",
|
| 5 |
-
"
|
| 6 |
-
"
|
| 7 |
-
"
|
| 8 |
-
"
|
| 9 |
-
"
|
| 10 |
-
"
|
|
|
|
| 11 |
},
|
| 12 |
"gates": {
|
| 13 |
"legado_legado_legado_calibracao_soundness.v1_calibracao": true,
|
|
@@ -95,65 +96,101 @@
|
|
| 95 |
"legado_q5_ablacao_moe_aprende_3x": true,
|
| 96 |
"legado_q5_ablacao_moe_domina_pequena_3x": true,
|
| 97 |
"legado_cargo_todos_verdes": true,
|
| 98 |
-
"
|
| 99 |
-
"
|
| 100 |
-
"
|
| 101 |
-
"
|
| 102 |
-
"
|
| 103 |
-
"
|
| 104 |
-
"
|
| 105 |
-
"
|
| 106 |
-
"
|
| 107 |
-
"
|
| 108 |
-
"
|
| 109 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
},
|
| 111 |
"medicoes": {
|
| 112 |
-
"
|
| 113 |
-
"
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
"
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
},
|
| 125 |
-
"
|
| 126 |
-
"
|
| 127 |
-
"
|
| 128 |
-
"
|
| 129 |
-
"
|
| 130 |
}
|
| 131 |
},
|
| 132 |
-
"
|
| 133 |
-
"
|
| 134 |
-
"
|
| 135 |
-
"
|
| 136 |
-
"speedup_zero_copia": 1.5606010214124844,
|
| 137 |
-
"razao_tensor_vs_zero_copia": 17.434798772862305
|
| 138 |
},
|
| 139 |
-
"
|
| 140 |
-
"
|
| 141 |
-
|
| 142 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
},
|
| 144 |
-
"
|
| 145 |
-
"
|
| 146 |
-
"
|
| 147 |
-
"
|
| 148 |
-
"ce_final": 3.9293200288500105,
|
| 149 |
-
"build": "khtst-rust-0.9.0"
|
| 150 |
-
},
|
| 151 |
-
"q5_compilacao": {
|
| 152 |
-
"dominio_compila": true,
|
| 153 |
-
"tempo_frio_dominio_s": 70,
|
| 154 |
-
"tempo_frio_completo_s": 85,
|
| 155 |
-
"overhead_python_pct": 17.6,
|
| 156 |
-
"nota": "alegação Qwen '≈40%' CORRIGIDA por medição: overhead real desta máquina ~18% (70s vs 85s, builds frios com candle igual)"
|
| 157 |
}
|
| 158 |
-
}
|
|
|
|
| 159 |
}
|
|
|
|
| 2 |
"versao": "0.9.0",
|
| 3 |
"blocos": {
|
| 4 |
"legado_v080": "85/85",
|
| 5 |
+
"c1_cfg_tipada": "2/2",
|
| 6 |
+
"c2c4_bench": "5/5",
|
| 7 |
+
"c3_numpy": "1/1",
|
| 8 |
+
"b1_gng_entropia": "2/2",
|
| 9 |
+
"c_thompson": "3/3",
|
| 10 |
+
"c_jepa_adaptativa": "3/3",
|
| 11 |
+
"cargo": "111/111"
|
| 12 |
},
|
| 13 |
"gates": {
|
| 14 |
"legado_legado_legado_calibracao_soundness.v1_calibracao": true,
|
|
|
|
| 96 |
"legado_q5_ablacao_moe_aprende_3x": true,
|
| 97 |
"legado_q5_ablacao_moe_domina_pequena_3x": true,
|
| 98 |
"legado_cargo_todos_verdes": true,
|
| 99 |
+
"c1_cfg_valido_funciona": true,
|
| 100 |
+
"c1_tipo_errado_rejeitado": true,
|
| 101 |
+
"c4_simd_bit_identico": true,
|
| 102 |
+
"c4_avx512_detectado": true,
|
| 103 |
+
"c2_nosso_adamw_nao_pior_candle": true,
|
| 104 |
+
"c4_alinhamento_garantido_64b": true,
|
| 105 |
+
"d3_bf16_nao_e_gratis": true,
|
| 106 |
+
"b3_paisagem_medi_da": true,
|
| 107 |
+
"b3_escalar_argmin_nao_perde": true,
|
| 108 |
+
"c3_numpy_nao_pior": true,
|
| 109 |
+
"b1_gng_piso_insere": true,
|
| 110 |
+
"b1_gng_portao_ativo": true,
|
| 111 |
+
"c_ts_ts_aprende": true,
|
| 112 |
+
"c_ts_ucb1_aprende": true,
|
| 113 |
+
"c_ts_melhor_braço_domina_ts": true,
|
| 114 |
+
"c_jepa_um_braco_bit_a_bit": true,
|
| 115 |
+
"c_jepa_multibraco_explora": true,
|
| 116 |
+
"c_jepa_sem_colapso": true,
|
| 117 |
+
"cargo_todos_verdes": true
|
| 118 |
},
|
| 119 |
"medicoes": {
|
| 120 |
+
"bench_v090": {
|
| 121 |
+
"adamw_bit_identico": true,
|
| 122 |
+
"adamw_candle_nn_ms_por_passo": 13.458667233333333,
|
| 123 |
+
"adamw_escalar_ms": 2.063466,
|
| 124 |
+
"adamw_nosso_ms_por_passo": 8.769088933333334,
|
| 125 |
+
"adamw_simd_ms": 1.9067,
|
| 126 |
+
"adamw_speedup": 1.0822184926836944,
|
| 127 |
+
"alinhado_endereco_mod64": true,
|
| 128 |
+
"avx512_disponivel": true,
|
| 129 |
+
"axpy_alinhado64_ms": 1.545529,
|
| 130 |
+
"axpy_desalinhado8_ms": 1.483379,
|
| 131 |
+
"axpy_malloc16_ms": 1.527501,
|
| 132 |
+
"bf16_vs_f32": 0.337350592360738,
|
| 133 |
+
"bmu_indices_iguais": "2000/2000",
|
| 134 |
+
"bmu_matmul_erro_max": 3.0706254676715616e-05,
|
| 135 |
+
"bmu_matmul_ms": 28.492511,
|
| 136 |
+
"bmu_rm_ms": 22.110711000000002,
|
| 137 |
+
"bmu_soa_ms": 19.194955999999998,
|
| 138 |
+
"bmu_speedup_matmul": 0.5374394696206312,
|
| 139 |
+
"bmu_speedup_rm": 0.6998381010904624,
|
| 140 |
+
"bmu_speedup_soa": 0.8061450101787158,
|
| 141 |
+
"bmu_vecvec_ms": 15.473918,
|
| 142 |
+
"candle_bf16_ms": 44.138138,
|
| 143 |
+
"candle_f32_ms": 14.890027,
|
| 144 |
+
"clip_escalar_ms": 0.364306,
|
| 145 |
+
"clip_simd_ms": 0.319045,
|
| 146 |
+
"clip_speedup": 1.1418640003761225,
|
| 147 |
+
"cpus": 2,
|
| 148 |
+
"nosso_vs_candle_nn": 1.53478512256545
|
| 149 |
+
},
|
| 150 |
+
"c3_fronteira": {
|
| 151 |
+
"lista_ms_524k_tokens": 5.828058399856673,
|
| 152 |
+
"numpy_ms_524k_tokens": 0.002901799962273799,
|
| 153 |
+
"razao_lista_vs_numpy": 2008.428725490061,
|
| 154 |
+
"metodo": "funções de diagnóstico isoladas (_fronteira_lista/_fronteira_np)"
|
| 155 |
+
},
|
| 156 |
+
"b1_gng": {
|
| 157 |
+
"padrao": {
|
| 158 |
+
"vertices": 30,
|
| 159 |
+
"eq": 0.456599430153386,
|
| 160 |
+
"insercoes": 28
|
| 161 |
},
|
| 162 |
+
"entropia_piso": {
|
| 163 |
+
"vertices": 8,
|
| 164 |
+
"eq": 0.6157532138586492,
|
| 165 |
+
"insercoes": 6,
|
| 166 |
+
"bloqueadas": 54
|
| 167 |
}
|
| 168 |
},
|
| 169 |
+
"thompson": {
|
| 170 |
+
"thompson": 0.0019499999999999997,
|
| 171 |
+
"ucb1": 0.015599999999999961,
|
| 172 |
+
"uniforme": 0.29992499999999256
|
|
|
|
|
|
|
| 173 |
},
|
| 174 |
+
"c_jepa_adaptativa": {
|
| 175 |
+
"jogadas": [
|
| 176 |
+
8,
|
| 177 |
+
29,
|
| 178 |
+
19,
|
| 179 |
+
4
|
| 180 |
+
],
|
| 181 |
+
"posterior": [
|
| 182 |
+
0.3,
|
| 183 |
+
0.4838709677419355,
|
| 184 |
+
0.42857142857142855,
|
| 185 |
+
0.16666666666666666
|
| 186 |
+
],
|
| 187 |
+
"queda_pct": -47.92939824526996
|
| 188 |
},
|
| 189 |
+
"simd_relatorio": {
|
| 190 |
+
"avx512_disponivel": true,
|
| 191 |
+
"chamadas_avx": 450,
|
| 192 |
+
"chamadas_escalar": 1400
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
}
|
| 194 |
+
},
|
| 195 |
+
"veredito": "104/104"
|
| 196 |
}
|
resultados/telemetria_fase_c.jsonl
CHANGED
|
@@ -1,168 +1,62 @@
|
|
| 1 |
{"tipo":"ajuste","dados":{"passo":0,"parametro":"fase_c_base","de":0.0,"para":1.0,"motivo":"carregado checkpoint da fase B: /home/z/my-project/khtst-rust/resultados/gerador_conceitos.json (ajuste fino, não do zero)"}}
|
| 2 |
-
{"tipo":"ajuste","dados":{"passo":0,"parametro":"pensamento_tau_c","de":0.3,"para":0.
|
| 3 |
-
{"tipo":"ajuste","dados":{"passo":0,"parametro":"pensamento_tau_c","de":0.
|
| 4 |
-
{"tipo":"ajuste","dados":{"passo":1,"parametro":"pensamento_tau_c","de":0.
|
| 5 |
-
{"tipo":"ajuste","dados":{"passo":2,"parametro":"pensamento_tau_c","de":0.
|
| 6 |
-
{"tipo":"ajuste","dados":{"passo":3,"parametro":"pensamento_tau_c","de":0.
|
| 7 |
-
{"tipo":"ajuste","dados":{"passo":4,"parametro":"pensamento_tau_c","de":0.
|
| 8 |
-
{"tipo":"ajuste","dados":{"passo":5,"parametro":"pensamento_tau_c","de":0.
|
| 9 |
-
{"tipo":"ajuste","dados":{"passo":6,"parametro":"pensamento_tau_c","de":0.
|
| 10 |
-
{"tipo":"ajuste","dados":{"passo":7,"parametro":"pensamento_tau_c","de":0.
|
| 11 |
-
{"tipo":"ajuste","dados":{"passo":8,"parametro":"pensamento_tau_c","de":0.
|
| 12 |
-
{"tipo":"ajuste","dados":{"passo":9,"parametro":"pensamento_tau_c","de":0.
|
| 13 |
-
{"tipo":"ajuste","dados":{"passo":10,"parametro":"pensamento_tau_c","de":0.
|
| 14 |
-
{"tipo":"ajuste","dados":{"passo":11,"parametro":"pensamento_tau_c","de":0.
|
| 15 |
-
{"tipo":"ajuste","dados":{"passo":12,"parametro":"pensamento_tau_c","de":0.
|
| 16 |
-
{"tipo":"ajuste","dados":{"passo":13,"parametro":"pensamento_tau_c","de":0.
|
| 17 |
-
{"tipo":"ajuste","dados":{"passo":14,"parametro":"pensamento_tau_c","de":0.
|
| 18 |
-
{"tipo":"ajuste","dados":{"passo":15,"parametro":"pensamento_tau_c","de":0.
|
| 19 |
-
{"tipo":"ajuste","dados":{"passo":15,"parametro":"pensamento_n_tentativas","de":4.0,"para":8.0,"motivo":"A-3 Hoeffding+acordo (â=0.
|
| 20 |
-
{"tipo":"ajuste","dados":{"passo":16,"parametro":"pensamento_tau_c","de":0.
|
| 21 |
-
{"tipo":"ajuste","dados":{"passo":17,"parametro":"pensamento_tau_c","de":0.
|
| 22 |
-
{"tipo":"ajuste","dados":{"passo":18,"parametro":"pensamento_tau_c","de":0.
|
| 23 |
-
{"tipo":"ajuste","dados":{"passo":19,"parametro":"pensamento_tau_c","de":0.
|
| 24 |
-
{"tipo":"ajuste","dados":{"passo":20,"parametro":"pensamento_tau_c","de":0.
|
| 25 |
-
{"tipo":"ajuste","dados":{"passo":21,"parametro":"pensamento_tau_c","de":0.
|
| 26 |
-
{"tipo":"ajuste","dados":{"passo":22,"parametro":"pensamento_tau_c","de":0.
|
| 27 |
-
{"tipo":"ajuste","dados":{"passo":23,"parametro":"pensamento_tau_c","de":0.
|
| 28 |
-
{"tipo":"ajuste","dados":{"passo":24,"parametro":"pensamento_tau_c","de":0.
|
| 29 |
-
{"tipo":"ajuste","dados":{"passo":25,"parametro":"pensamento_tau_c","de":0.
|
| 30 |
-
{"tipo":"ajuste","dados":{"passo":26,"parametro":"pensamento_tau_c","de":0.
|
| 31 |
-
{"tipo":"ajuste","dados":{"passo":27,"parametro":"pensamento_tau_c","de":0.
|
| 32 |
-
{"tipo":"ajuste","dados":{"passo":28,"parametro":"pensamento_tau_c","de":0.
|
| 33 |
-
{"tipo":"ajuste","dados":{"passo":29,"parametro":"pensamento_tau_c","de":0.
|
| 34 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 35 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 36 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 37 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 38 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 39 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 40 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 41 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 42 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 43 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 44 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 45 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 46 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 47 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 48 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 49 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 50 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 51 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 52 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 53 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 54 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 55 |
-
{"tipo":"ajuste","dados":{"passo":53,"parametro":"pensamento_tau_c","de":0.
|
| 56 |
-
{"tipo":"ajuste","dados":{"passo":54,"parametro":"pensamento_tau_c","de":0.
|
| 57 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 58 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 59 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 60 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 61 |
-
{"tipo":"ajuste","dados":{"passo":
|
| 62 |
-
{"tipo":"ajuste","dados":{"passo":62,"parametro":"pensamento_tau_c","de":0.016592085361480713,"para":0.01657843589782715,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=63488)"}}
|
| 63 |
-
{"tipo":"ajuste","dados":{"passo":62,"parametro":"pensamento_gate_g","de":0.0,"para":0.1,"motivo":"A-5 Bernstein empírico (LCB=0.0014 > 0, média=0.1469)"}}
|
| 64 |
-
{"tipo":"ajuste","dados":{"passo":63,"parametro":"pensamento_tau_c","de":0.01657843589782715,"para":0.016591370105743408,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=64512)"}}
|
| 65 |
-
{"tipo":"ajuste","dados":{"passo":63,"parametro":"pensamento_gate_g","de":0.1,"para":0.2,"motivo":"A-5 Bernstein empírico (LCB=0.0040 > 0, média=0.1472)"}}
|
| 66 |
-
{"tipo":"ajuste","dados":{"passo":64,"parametro":"pensamento_tau_c","de":0.016591370105743408,"para":0.016611099243164062,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=65536)"}}
|
| 67 |
-
{"tipo":"ajuste","dados":{"passo":64,"parametro":"pensamento_gate_g","de":0.2,"para":0.30000000000000004,"motivo":"A-5 Bernstein empírico (LCB=0.0064 > 0, média=0.1474)"}}
|
| 68 |
-
{"tipo":"ajuste","dados":{"passo":65,"parametro":"pensamento_tau_c","de":0.016611099243164062,"para":0.016618072986602783,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=66560)"}}
|
| 69 |
-
{"tipo":"ajuste","dados":{"passo":65,"parametro":"pensamento_gate_g","de":0.30000000000000004,"para":0.4,"motivo":"A-5 Bernstein empírico (LCB=0.0088 > 0, média=0.1478)"}}
|
| 70 |
-
{"tipo":"ajuste","dados":{"passo":66,"parametro":"pensamento_gate_g","de":0.4,"para":0.5,"motivo":"A-5 Bernstein empírico (LCB=0.0111 > 0, média=0.1479)"}}
|
| 71 |
-
{"tipo":"ajuste","dados":{"passo":67,"parametro":"pensamento_tau_c","de":0.016618072986602783,"para":0.01663726568222046,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=68608)"}}
|
| 72 |
-
{"tipo":"ajuste","dados":{"passo":67,"parametro":"pensamento_gate_g","de":0.5,"para":0.6,"motivo":"A-5 Bernstein empírico (LCB=0.0133 > 0, média=0.1480)"}}
|
| 73 |
-
{"tipo":"ajuste","dados":{"passo":68,"parametro":"pensamento_tau_c","de":0.01663726568222046,"para":0.016634821891784668,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=69632)"}}
|
| 74 |
-
{"tipo":"ajuste","dados":{"passo":68,"parametro":"pensamento_gate_g","de":0.6,"para":0.7,"motivo":"A-5 Bernstein empírico (LCB=0.0151 > 0, média=0.1479)"}}
|
| 75 |
-
{"tipo":"ajuste","dados":{"passo":69,"parametro":"pensamento_gate_g","de":0.7,"para":0.7999999999999999,"motivo":"A-5 Bernstein empírico (LCB=0.0172 > 0, média=0.1481)"}}
|
| 76 |
-
{"tipo":"ajuste","dados":{"passo":70,"parametro":"pensamento_gate_g","de":0.7999999999999999,"para":0.8999999999999999,"motivo":"A-5 Bernstein empírico (LCB=0.0190 > 0, média=0.1480)"}}
|
| 77 |
-
{"tipo":"ajuste","dados":{"passo":71,"parametro":"pensamento_tau_c","de":0.016634821891784668,"para":0.01662588119506836,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=72704)"}}
|
| 78 |
-
{"tipo":"ajuste","dados":{"passo":71,"parametro":"pensamento_gate_g","de":0.8999999999999999,"para":0.9999999999999999,"motivo":"A-5 Bernstein empírico (LCB=0.0208 > 0, média=0.1480)"}}
|
| 79 |
-
{"tipo":"ajuste","dados":{"passo":72,"parametro":"pensamento_tau_c","de":0.01662588119506836,"para":0.016639411449432373,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=73728)"}}
|
| 80 |
-
{"tipo":"ajuste","dados":{"passo":72,"parametro":"pensamento_gate_g","de":0.9999999999999999,"para":1.0,"motivo":"A-5 Bernstein empírico (LCB=0.0228 > 0, média=0.1481)"}}
|
| 81 |
-
{"tipo":"ajuste","dados":{"passo":73,"parametro":"pensamento_tau_c","de":0.016639411449432373,"para":0.01664215326309204,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=74752)"}}
|
| 82 |
-
{"tipo":"ajuste","dados":{"passo":74,"parametro":"pensamento_tau_c","de":0.01664215326309204,"para":0.016638875007629395,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=75776)"}}
|
| 83 |
-
{"tipo":"ajuste","dados":{"passo":75,"parametro":"pensamento_tau_c","de":0.016638875007629395,"para":0.01664435863494873,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=76800)"}}
|
| 84 |
-
{"tipo":"ajuste","dados":{"passo":76,"parametro":"pensamento_tau_c","de":0.01664435863494873,"para":0.01665741205215454,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=77824)"}}
|
| 85 |
-
{"tipo":"ajuste","dados":{"passo":77,"parametro":"pensamento_tau_c","de":0.01665741205215454,"para":0.016666710376739502,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=78848)"}}
|
| 86 |
-
{"tipo":"ajuste","dados":{"passo":78,"parametro":"pensamento_tau_c","de":0.016666710376739502,"para":0.016673803329467773,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=79872)"}}
|
| 87 |
-
{"tipo":"ajuste","dados":{"passo":79,"parametro":"pensamento_tau_c","de":0.016673803329467773,"para":0.016688168048858643,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=80896)"}}
|
| 88 |
-
{"tipo":"ajuste","dados":{"passo":80,"parametro":"pensamento_tau_c","de":0.016688168048858643,"para":0.016695618629455566,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=81920)"}}
|
| 89 |
-
{"tipo":"ajuste","dados":{"passo":81,"parametro":"pensamento_tau_c","de":0.016695618629455566,"para":0.016701817512512207,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=82944)"}}
|
| 90 |
-
{"tipo":"ajuste","dados":{"passo":82,"parametro":"pensamento_tau_c","de":0.016701817512512207,"para":0.01669764518737793,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=83968)"}}
|
| 91 |
-
{"tipo":"ajuste","dados":{"passo":83,"parametro":"pensamento_tau_c","de":0.01669764518737793,"para":0.0167122483253479,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=84992)"}}
|
| 92 |
-
{"tipo":"ajuste","dados":{"passo":84,"parametro":"pensamento_tau_c","de":0.0167122483253479,"para":0.01671600341796875,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=86016)"}}
|
| 93 |
-
{"tipo":"ajuste","dados":{"passo":85,"parametro":"pensamento_tau_c","de":0.01671600341796875,"para":0.01671910285949707,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=87040)"}}
|
| 94 |
-
{"tipo":"ajuste","dados":{"passo":86,"parametro":"pensamento_tau_c","de":0.01671910285949707,"para":0.016711294651031494,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=88064)"}}
|
| 95 |
-
{"tipo":"ajuste","dados":{"passo":87,"parametro":"pensamento_tau_c","de":0.016711294651031494,"para":0.016720473766326904,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=89088)"}}
|
| 96 |
-
{"tipo":"ajuste","dados":{"passo":88,"parametro":"pensamento_tau_c","de":0.016720473766326904,"para":0.01672607660293579,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=90112)"}}
|
| 97 |
-
{"tipo":"ajuste","dados":{"passo":89,"parametro":"pensamento_tau_c","de":0.01672607660293579,"para":0.016732096672058105,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=91136)"}}
|
| 98 |
-
{"tipo":"ajuste","dados":{"passo":90,"parametro":"pensamento_tau_c","de":0.016732096672058105,"para":0.01672607660293579,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=92160)"}}
|
| 99 |
-
{"tipo":"ajuste","dados":{"passo":91,"parametro":"pensamento_tau_c","de":0.01672607660293579,"para":0.016723573207855225,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=93184)"}}
|
| 100 |
-
{"tipo":"ajuste","dados":{"passo":92,"parametro":"pensamento_tau_c","de":0.016723573207855225,"para":0.016735374927520752,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=94208)"}}
|
| 101 |
-
{"tipo":"ajuste","dados":{"passo":93,"parametro":"pensamento_tau_c","de":0.016735374927520752,"para":0.016741812229156494,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=95232)"}}
|
| 102 |
-
{"tipo":"ajuste","dados":{"passo":94,"parametro":"pensamento_tau_c","de":0.016741812229156494,"para":0.016735613346099854,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=96256)"}}
|
| 103 |
-
{"tipo":"ajuste","dados":{"passo":95,"parametro":"pensamento_tau_c","de":0.016735613346099854,"para":0.016741573810577393,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=97280)"}}
|
| 104 |
-
{"tipo":"ajuste","dados":{"passo":98,"parametro":"pensamento_tau_c","de":0.016741573810577393,"para":0.01675236225128174,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=98304)"}}
|
| 105 |
-
{"tipo":"ajuste","dados":{"passo":99,"parametro":"pensamento_tau_c","de":0.01675236225128174,"para":0.016757845878601074,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=99328)"}}
|
| 106 |
-
{"tipo":"ajuste","dados":{"passo":100,"parametro":"pensamento_tau_c","de":0.016757845878601074,"para":0.016761422157287598,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=100352)"}}
|
| 107 |
-
{"tipo":"ajuste","dados":{"passo":101,"parametro":"pensamento_tau_c","de":0.016761422157287598,"para":0.01675891876220703,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=101376)"}}
|
| 108 |
-
{"tipo":"ajuste","dados":{"passo":102,"parametro":"pensamento_tau_c","de":0.01675891876220703,"para":0.01677107810974121,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=102400)"}}
|
| 109 |
-
{"tipo":"ajuste","dados":{"passo":104,"parametro":"pensamento_tau_c","de":0.01677107810974121,"para":0.016778409481048584,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=103424)"}}
|
| 110 |
-
{"tipo":"ajuste","dados":{"passo":105,"parametro":"pensamento_tau_c","de":0.016778409481048584,"para":0.016786634922027588,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=104448)"}}
|
| 111 |
-
{"tipo":"ajuste","dados":{"passo":106,"parametro":"pensamento_tau_c","de":0.016786634922027588,"para":0.016798019409179688,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=105472)"}}
|
| 112 |
-
{"tipo":"ajuste","dados":{"passo":107,"parametro":"pensamento_tau_c","de":0.016798019409179688,"para":0.0168115496635437,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=106496)"}}
|
| 113 |
-
{"tipo":"ajuste","dados":{"passo":108,"parametro":"pensamento_tau_c","de":0.0168115496635437,"para":0.016808629035949707,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=107520)"}}
|
| 114 |
-
{"tipo":"ajuste","dados":{"passo":109,"parametro":"pensamento_tau_c","de":0.016808629035949707,"para":0.016813039779663086,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=108544)"}}
|
| 115 |
-
{"tipo":"ajuste","dados":{"passo":110,"parametro":"pensamento_tau_c","de":0.016813039779663086,"para":0.016818881034851074,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=109568)"}}
|
| 116 |
-
{"tipo":"ajuste","dados":{"passo":112,"parametro":"pensamento_tau_c","de":0.016818881034851074,"para":0.016836047172546387,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=111616)"}}
|
| 117 |
-
{"tipo":"ajuste","dados":{"passo":113,"parametro":"pensamento_tau_c","de":0.016836047172546387,"para":0.016848087310791016,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=112640)"}}
|
| 118 |
-
{"tipo":"ajuste","dados":{"passo":114,"parametro":"pensamento_tau_c","de":0.016848087310791016,"para":0.01686161756515503,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=113664)"}}
|
| 119 |
-
{"tipo":"ajuste","dados":{"passo":115,"parametro":"pensamento_tau_c","de":0.01686161756515503,"para":0.0168687105178833,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=114688)"}}
|
| 120 |
-
{"tipo":"ajuste","dados":{"passo":116,"parametro":"pensamento_tau_c","de":0.0168687105178833,"para":0.016875267028808594,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=115712)"}}
|
| 121 |
-
{"tipo":"ajuste","dados":{"passo":117,"parametro":"pensamento_tau_c","de":0.016875267028808594,"para":0.016874253749847412,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=116736)"}}
|
| 122 |
-
{"tipo":"ajuste","dados":{"passo":118,"parametro":"pensamento_tau_c","de":0.016874253749847412,"para":0.0168837308883667,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=117760)"}}
|
| 123 |
-
{"tipo":"ajuste","dados":{"passo":119,"parametro":"pensamento_tau_c","de":0.0168837308883667,"para":0.01688694953918457,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=118784)"}}
|
| 124 |
-
{"tipo":"ajuste","dados":{"passo":120,"parametro":"pensamento_tau_c","de":0.01688694953918457,"para":0.016893506050109863,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=119808)"}}
|
| 125 |
-
{"tipo":"ajuste","dados":{"passo":122,"parametro":"pensamento_tau_c","de":0.016893506050109863,"para":0.016901671886444092,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=121856)"}}
|
| 126 |
-
{"tipo":"ajuste","dados":{"passo":123,"parametro":"pensamento_tau_c","de":0.016901671886444092,"para":0.016905546188354492,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=122880)"}}
|
| 127 |
-
{"tipo":"ajuste","dados":{"passo":124,"parametro":"pensamento_tau_c","de":0.016905546188354492,"para":0.016914963722229004,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=123904)"}}
|
| 128 |
-
{"tipo":"ajuste","dados":{"passo":125,"parametro":"pensamento_tau_c","de":0.016914963722229004,"para":0.01692676544189453,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=124928)"}}
|
| 129 |
-
{"tipo":"ajuste","dados":{"passo":126,"parametro":"pensamento_tau_c","de":0.01692676544189453,"para":0.01692938804626465,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=125952)"}}
|
| 130 |
-
{"tipo":"ajuste","dados":{"passo":127,"parametro":"pensamento_tau_c","de":0.01692938804626465,"para":0.01693546772003174,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=126976)"}}
|
| 131 |
-
{"tipo":"ajuste","dados":{"passo":128,"parametro":"pensamento_tau_c","de":0.01693546772003174,"para":0.016948997974395752,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=128000)"}}
|
| 132 |
-
{"tipo":"ajuste","dados":{"passo":129,"parametro":"pensamento_tau_c","de":0.016948997974395752,"para":0.016956567764282227,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=129024)"}}
|
| 133 |
-
{"tipo":"ajuste","dados":{"passo":130,"parametro":"pensamento_tau_c","de":0.016956567764282227,"para":0.016968727111816406,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=130048)"}}
|
| 134 |
-
{"tipo":"ajuste","dados":{"passo":134,"parametro":"pensamento_tau_c","de":0.016968727111816406,"para":0.01697641611099243,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=132096)"}}
|
| 135 |
-
{"tipo":"ajuste","dados":{"passo":137,"parametro":"pensamento_tau_c","de":0.01697641611099243,"para":0.016979634761810303,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=134144)"}}
|
| 136 |
-
{"tipo":"ajuste","dados":{"passo":138,"parametro":"pensamento_tau_c","de":0.016979634761810303,"para":0.016981422901153564,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=135168)"}}
|
| 137 |
-
{"tipo":"ajuste","dados":{"passo":139,"parametro":"pensamento_tau_c","de":0.016981422901153564,"para":0.01699155569076538,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=136192)"}}
|
| 138 |
-
{"tipo":"ajuste","dados":{"passo":140,"parametro":"pensamento_tau_c","de":0.01699155569076538,"para":0.0169944167137146,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=137216)"}}
|
| 139 |
-
{"tipo":"ajuste","dados":{"passo":141,"parametro":"pensamento_tau_c","de":0.0169944167137146,"para":0.017006099224090576,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=138240)"}}
|
| 140 |
-
{"tipo":"ajuste","dados":{"passo":142,"parametro":"pensamento_tau_c","de":0.017006099224090576,"para":0.017008602619171143,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=139264)"}}
|
| 141 |
-
{"tipo":"ajuste","dados":{"passo":143,"parametro":"pensamento_tau_c","de":0.017008602619171143,"para":0.017015516757965088,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=140288)"}}
|
| 142 |
-
{"tipo":"ajuste","dados":{"passo":145,"parametro":"pensamento_tau_c","de":0.017015516757965088,"para":0.017021477222442627,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=141312)"}}
|
| 143 |
-
{"tipo":"ajuste","dados":{"passo":146,"parametro":"pensamento_tau_c","de":0.017021477222442627,"para":0.01702880859375,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=142336)"}}
|
| 144 |
-
{"tipo":"ajuste","dados":{"passo":148,"parametro":"pensamento_tau_c","de":0.01702880859375,"para":0.01703542470932007,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=143360)"}}
|
| 145 |
-
{"tipo":"ajuste","dados":{"passo":149,"parametro":"pensamento_tau_c","de":0.01703542470932007,"para":0.017037272453308105,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=144384)"}}
|
| 146 |
-
{"tipo":"ajuste","dados":{"passo":151,"parametro":"pensamento_tau_c","de":0.017037272453308105,"para":0.017041385173797607,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=145408)"}}
|
| 147 |
-
{"tipo":"ajuste","dados":{"passo":152,"parametro":"pensamento_tau_c","de":0.017041385173797607,"para":0.017049670219421387,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=146432)"}}
|
| 148 |
-
{"tipo":"ajuste","dados":{"passo":153,"parametro":"pensamento_tau_c","de":0.017049670219421387,"para":0.017046570777893066,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=147456)"}}
|
| 149 |
-
{"tipo":"ajuste","dados":{"passo":154,"parametro":"pensamento_tau_c","de":0.017046570777893066,"para":0.017054259777069092,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=148480)"}}
|
| 150 |
-
{"tipo":"ajuste","dados":{"passo":156,"parametro":"pensamento_tau_c","de":0.017054259777069092,"para":0.01706010103225708,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=149504)"}}
|
| 151 |
-
{"tipo":"ajuste","dados":{"passo":157,"parametro":"pensamento_tau_c","de":0.01706010103225708,"para":0.017068684101104736,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=150528)"}}
|
| 152 |
-
{"tipo":"ajuste","dados":{"passo":158,"parametro":"pensamento_tau_c","de":0.017068684101104736,"para":0.0170784592628479,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=151552)"}}
|
| 153 |
-
{"tipo":"ajuste","dados":{"passo":160,"parametro":"pensamento_tau_c","de":0.0170784592628479,"para":0.01708817481994629,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=152576)"}}
|
| 154 |
-
{"tipo":"ajuste","dados":{"passo":162,"parametro":"pensamento_tau_c","de":0.01708817481994629,"para":0.017090141773223877,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=153600)"}}
|
| 155 |
-
{"tipo":"ajuste","dados":{"passo":164,"parametro":"pensamento_tau_c","de":0.017090141773223877,"para":0.0170973539352417,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=154624)"}}
|
| 156 |
-
{"tipo":"ajuste","dados":{"passo":165,"parametro":"pensamento_tau_c","de":0.0170973539352417,"para":0.017104685306549072,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=155648)"}}
|
| 157 |
-
{"tipo":"ajuste","dados":{"passo":166,"parametro":"pensamento_tau_c","de":0.017104685306549072,"para":0.017107903957366943,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=156672)"}}
|
| 158 |
-
{"tipo":"ajuste","dados":{"passo":167,"parametro":"pensamento_tau_c","de":0.017107903957366943,"para":0.017113983631134033,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=157696)"}}
|
| 159 |
-
{"tipo":"ajuste","dados":{"passo":168,"parametro":"pensamento_tau_c","de":0.017113983631134033,"para":0.017116248607635498,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=158720)"}}
|
| 160 |
-
{"tipo":"ajuste","dados":{"passo":170,"parametro":"pensamento_tau_c","de":0.017116248607635498,"para":0.017122328281402588,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=159744)"}}
|
| 161 |
-
{"tipo":"ajuste","dados":{"passo":171,"parametro":"pensamento_tau_c","de":0.017122328281402588,"para":0.017128288745880127,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=160768)"}}
|
| 162 |
-
{"tipo":"ajuste","dados":{"passo":172,"parametro":"pensamento_tau_c","de":0.017128288745880127,"para":0.017133712768554688,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=161792)"}}
|
| 163 |
-
{"tipo":"ajuste","dados":{"passo":173,"parametro":"pensamento_tau_c","de":0.017133712768554688,"para":0.017142534255981445,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=162816)"}}
|
| 164 |
-
{"tipo":"ajuste","dados":{"passo":174,"parametro":"pensamento_tau_c","de":0.017142534255981445,"para":0.017145395278930664,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=163840)"}}
|
| 165 |
-
{"tipo":"ajuste","dados":{"passo":175,"parametro":"pensamento_tau_c","de":0.017145395278930664,"para":0.01715296506881714,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=164864)"}}
|
| 166 |
-
{"tipo":"ajuste","dados":{"passo":176,"parametro":"pensamento_tau_c","de":0.01715296506881714,"para":0.01715928316116333,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=165888)"}}
|
| 167 |
-
{"tipo":"ajuste","dados":{"passo":179,"parametro":"pensamento_tau_c","de":0.01715928316116333,"para":0.0171622633934021,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=166912)"}}
|
| 168 |
-
{"tipo":"dump","dados":{"acordo_tentativas":[[0,0.5028600002309153],[1,0.4950322823521371],[2,0.4798875816486543],[3,0.4858313038324316],[4,0.4936330304990406],[5,0.48478373412429693],[6,0.5102738069156961],[7,0.495097345643444],[8,0.5009391059769163],[9,0.5067074737938432],[10,0.500634032379215],[11,0.4896627690953513],[12,0.49489349219948053],[13,0.5049056810579108],[14,0.5087293806524636],[15,0.5105456787277944],[16,0.5218364807563679],[17,0.5271108039014507],[18,0.5000031566790616],[19,0.5037049457702475],[20,0.4917097393918084],[21,0.4977927643897904],[22,0.5092931723047514],[23,0.48343566594606574],[24,0.4780086800213515],[25,0.5014162051957101],[26,0.4959500899810034],[27,0.5226301547275701],[28,0.5140384140880391],[29,0.5112525659399884],[30,0.5282249843755077],[31,0.4947323422044671],[32,0.5026412593967203],[33,0.5078339009002472],[34,0.5033388147906711],[35,0.49501056362836],[36,0.5126274524227483],[37,0.5064064586549648],[38,0.509220097194581],[39,0.4843062834115699],[40,0.48344614257803187],[41,0.5076655454662008],[42,0.501095445196673],[43,0.5232622742769308],[44,0.5078513798847174],[45,0.5061007815044528],[46,0.5147967577019396],[47,0.5136789209209383],[48,0.511277187052959],[49,0.49925600617643795],[50,0.4928130326346339],[51,0.5288023574006123],[52,0.49586504269124515],[53,0.5024730978402658],[54,0.501211090372332],[55,0.515379966624702],[56,0.49900681593377766],[58,0.5057968174611839],[59,0.5093751461924209],[60,0.5067863393038957],[61,0.5003585508820834],[62,0.4947806589237492],[63,0.5174476473863857],[64,0.49470607010395423],[65,0.5107765162247233],[66,0.4947440256655682],[67,0.5010196952110467],[68,0.49608979225255706],[69,0.5051402508033789],[70,0.5137791107263183],[71,0.5181295310903806],[72,0.5192325501363181],[73,0.49485721812622313],[74,0.48922642430625274],[75,0.5143582477467135],[76,0.5032481459590296],[77,0.49706912707188167],[78,0.49314710880328977],[79,0.4862163895838118],[80,0.4945040759630501],[81,0.5050333777338286],[82,0.5049005637799079],[83,0.4902614629342376],[84,0.48436840685705346],[85,0.5140893314480005],[86,0.5155901123944204],[87,0.5233347928927591],[88,0.5012184785058101],[89,0.4908947451040149],[90,0.5100736886815866],[91,0.4889509727800032],[92,0.5040305781682642],[93,0.5051442451173594],[94,0.5219239644162977],[95,0.5137179221977325],[98,0.4977410391923816],[99,0.51080007784185],[100,0.5047323306037773],[101,0.5165671084056763],[102,0.5061268517941547],[104,0.506462094160573],[105,0.5156913814425934],[106,0.49386223500672105],[107,0.5122764733290145],[108,0.5107483759250803],[109,0.5228661045548506],[110,0.510680131187352],[111,0.5108237865836903],[112,0.5198076062758142],[113,0.5065654440938184],[114,0.5054116566946808],[115,0.49691915108511847],[116,0.4982414208401072],[117,0.5000035631625602],[118,0.492798294503397],[119,0.49849974115689594],[120,0.5035712090320885],[121,0.5118594358597571],[122,0.5262012491839414],[123,0.5036681105072299],[124,0.5082135452394141],[125,0.5122260597418062],[126,0.5094843112650173],[127,0.5100657866181185],[128,0.510991378538165],[129,0.5094215934126017],[130,0.5002903601174088],[132,0.486712965250869],[134,0.5094546760374215],[136,0.5088323370049087],[137,0.4871564242785098],[138,0.5067987435031682],[139,0.4897583644584908],[140,0.4905160826650293],[141,0.48622328873413306],[142,0.5037654667321476],[143,0.4887495086780594],[145,0.504955483134836],[146,0.5013548591911482],[148,0.5044902188140744],[149,0.5191630411718506],[151,0.5006109667010605],[152,0.5045971653986877],[153,0.4956807148021956],[154,0.5053270674155405],[156,0.5141158875388404],[157,0.5150347594365788],[158,0.5146901643214127],[160,0.5054877389823863],[162,0.4907115531192782],[164,0.48217524548090296],[165,0.5022985541339343],[166,0.5111773851846616],[167,0.5081046163880577],[168,0.49784239246703993],[170,0.4983166469367764],[171,0.49231743154329405],[172,0.4973643472355131],[173,0.5257275487116809],[174,0.5086705264887618],[175,0.4988940144733836],[176,0.5107558603243282],[179,0.48784584764265065]],"ce_gatilhada_com":[[0,3.990682522455851],[1,4.172070026397705],[2,3.9922971725463867],[3,4.024745285511017],[4,4.040532827377319],[5,3.9871789614359536],[6,3.901935895284017],[7,4.017208576202393],[8,3.9832544922828674],[9,3.995508829752604],[10,4.048591196537018],[11,4.054214318593343],[12,4.201066335042317],[13,3.852038542429606],[14,4.049705346425374],[15,4.025229334831238],[16,4.368491172790527],[17,4.018544673919678],[18,3.85960324605306],[19,4.031909942626953],[20,4.01624608039856],[21,4.042102694511414],[22,4.215875864028931],[23,3.86575984954834],[24,4.182933509349823],[25,3.900304079055786],[26,4.225833177566528],[27,4.212203184763591],[28,4.296268701553345],[29,3.779969811439514],[30,4.043873906135559],[31,4.446818113327026],[32,4.050361553827922],[33,3.8948398431142173],[34,4.043341716130574],[35,3.7278977235158286],[36,4.134139855702718],[37,4.081378757953644],[38,4.045528471469879],[39,4.0925867557525635],[40,4.220437208811442],[41,4.1184563636779785],[42,3.9894015789031982],[43,3.908494234085083],[44,3.9376860857009888],[45,4.0315806070963545],[46,4.037987470626831],[47,4.160836219787598],[48,3.9042978286743164],[49,4.360845327377319],[50,3.9469112157821655],[51,3.711232304573059],[52,4.006168365478516],[53,4.096292177836101],[54,3.984761118888855],[55,4.017864306767781],[56,4.275344053904216],[58,4.67264986038208],[59,3.9628067016601562],[60,4.173342227935791],[61,4.021025975545247],[62,3.877448260784149],[63,4.1110148429870605],[64,3.7457308769226074],[65,4.00639283657074],[66,4.014901320139567],[67,3.7936489582061768],[68,3.992401123046875],[69,4.0935810804367065],[70,4.29033621152242],[71,4.0560877323150635],[72,3.7011795043945312],[73,3.845238208770752],[74,4.254753351211548],[75,4.245847225189209],[76,4.057919025421143],[77,4.2678611278533936],[78,3.8418723344802856],[79,4.271665573120117],[80,4.192492604255676],[81,3.8606152534484863],[82,3.883606751759847],[83,3.9665369987487793],[84,4.130507588386536],[85,4.143632968266805],[86,4.178323566913605],[87,3.69917893409729],[88,4.259217858314514],[89,4.011404395103455],[90,3.9650556445121765],[91,4.109483599662781],[92,4.101954460144043],[93,4.351506233215332],[94,4.01691746711731],[95,3.7731308937072754],[98,4.640173435211182],[99,4.044714093208313],[100,3.9896199703216553],[101,4.109546264012654],[102,4.145634651184082],[104,4.106081008911133],[105,3.9364733695983887],[106,4.039921402931213],[107,3.739548683166504],[108,4.034179608027141],[109,4.102705001831055],[110,4.072922587394714],[111,4.171568393707275],[112,3.7461421489715576],[113,4.1238484382629395],[114,4.109546184539795],[115,4.035416960716248],[116,3.918515920639038],[117,4.144512335459392],[118,3.932486891746521],[119,3.9242016474405923],[120,4.145139217376709],[121,4.045098940531413],[122,3.9033401012420654],[123,3.9184993505477905],[124,4.327580451965332],[125,3.9230809211730957],[126,4.2412800788879395],[127,4.00946569442749],[128,3.933870315551758],[129,4.072670221328735],[130,3.8194403648376465],[132,4.017874240875244],[134,4.273417949676514],[136,4.176693757375081],[137,4.235759496688843],[138,3.985278765360514],[139,3.7222108840942383],[140,4.34234356880188],[141,4.404490947723389],[142,4.373222351074219],[143,4.517516613006592],[145,4.281197786331177],[146,4.353374004364014],[148,4.205049514770508],[149,4.130898594856262],[151,4.001789887746175],[152,4.291512489318848],[153,3.964293599128723],[154,3.9655206203460693],[156,4.505177974700928],[157,4.049394130706787],[158,4.10487174987793],[160,3.9899709224700928],[162,3.9660974740982056],[164,3.7900190353393555],[165,4.037952899932861],[166,3.996012528737386],[167,4.371770858764648],[168,4.122939586639404],[170,4.000663995742798],[171,3.9514858722686768],[172,3.932365655899048],[173,4.118404865264893],[174,4.402906179428101],[175,3.99057936668396],[176,4.321465492248535],[179,4.181258678436279]],"ce_gatilhada_sem":[[0,4.101917902628581],[1,4.163182338078816],[2,3.9917688369750977],[3,4.061686992645264],[4,3.914612134297689],[5,3.9954094092051187],[6,3.911052703857422],[7,4.033169865608215],[8,4.016918420791626],[9,3.874128739039103],[10,4.0294270515441895],[11,3.9873576164245605],[12,4.171914180119832],[13,3.8939428329467773],[14,4.151257197062175],[15,3.9584625959396362],[16,4.402843952178955],[17,3.9543723265329995],[18,3.7655808130900064],[19,3.995170513788859],[20,4.079830288887024],[21,4.120199203491211],[22,4.174338638782501],[23,3.6481034755706787],[24,4.179996907711029],[25,3.8647427558898926],[26,4.139061689376831],[27,4.2459917068481445],[28,4.211869120597839],[29,3.7684987783432007],[30,4.1127729415893555],[31,4.477316379547119],[32,3.9516282081604004],[33,3.896077315012614],[34,3.938241720199585],[35,3.6764734586079917],[36,4.055819829305013],[37,4.0684592723846436],[38,3.9777721166610718],[39,4.062942206859589],[40,4.3250861167907715],[41,4.116140365600586],[42,4.0153621435165405],[43,3.8735488255818686],[44,3.9291712045669556],[45,4.013108730316162],[46,3.8660807609558105],[47,4.211712121963501],[48,3.9453582763671875],[49,4.298929929733276],[50,3.8404853343963623],[51,3.660251498222351],[52,4.019053141276042],[53,4.172148704528809],[54,4.147598385810852],[55,4.028499603271484],[56,4.277413527170817],[58,4.535336971282959],[59,3.892958641052246],[60,4.298323631286621],[61,4.021728992462158],[62,3.8642889261245728],[63,4.216510772705078],[64,3.652679681777954],[65,3.9728922843933105],[66,3.984992424647013],[67,3.8643219470977783],[68,4.017503420511882],[69,3.955687165260315],[70,4.221375862757365],[71,4.024052202701569],[72,3.7316792011260986],[73,3.7983953952789307],[74,4.257179856300354],[75,4.278022050857544],[76,4.021566867828369],[77,4.28641152381897],[78,3.969394564628601],[79,4.215531349182129],[80,4.160964012145996],[81,3.792202949523926],[82,3.9725190003712973],[83,3.934025764465332],[84,3.979703664779663],[85,4.03809928894043],[86,4.1925161480903625],[87,3.682358980178833],[88,4.250956296920776],[89,4.033212065696716],[90,3.8874682188034058],[91,4.179266810417175],[92,4.154380798339844],[93,4.3096184730529785],[94,3.988636791706085],[95,3.7214863300323486],[98,4.707629203796387],[99,4.0041927099227905],[100,3.8919597466786704],[101,4.103201866149902],[102,4.155486583709717],[104,4.151379108428955],[105,3.86596417427063],[106,3.944948434829712],[107,3.703117609024048],[108,3.9001522064208984],[109,4.073512315750122],[110,4.1418023109436035],[111,4.192521731058757],[112,3.5740835666656494],[113,4.0821919441223145],[114,4.167129993438721],[115,4.071088671684265],[116,3.785374402999878],[117,4.077940940856934],[118,3.9024194478988647],[119,3.871340831120809],[120,4.138059616088867],[121,4.075603087743123],[122,3.9188358783721924],[123,3.818215250968933],[124,4.307414531707764],[125,3.7961649894714355],[126,4.283974965413411],[127,4.004149556159973],[128,3.859858512878418],[129,4.08201789855957],[130,3.8480067253112793],[132,4.0343849658966064],[134,4.466161727905273],[136,4.151505629221599],[137,4.296725511550903],[138,3.970802386601766],[139,3.5077567100524902],[140,4.3825390338897705],[141,4.433453559875488],[142,4.3604656457901],[143,4.707491874694824],[145,4.377482891082764],[146,4.278686046600342],[148,4.204703092575073],[149,4.106816053390503],[151,4.095471064249675],[152,4.223231196403503],[153,3.9363183975219727],[154,3.9086225032806396],[156,4.421633720397949],[157,4.111346244812012],[158,4.1443915367126465],[160,4.035621643066406],[162,4.007079482078552],[164,4.060975551605225],[165,3.87597918510437],[166,3.9723476568857827],[167,4.324607849121094],[168,4.129934310913086],[170,4.045195460319519],[171,3.892066240310669],[172,3.898447275161743],[173,4.213126182556152],[174,4.3643341064453125],[175,3.981333017349243],[176,4.308775424957275],[179,4.270479917526245]],"confianca_media":[[0,0.016349429672118276],[1,0.01628106931457296],[2,0.016283084405586123],[3,0.016030373983085155],[4,0.016135476995259523],[5,0.0161596552352421],[6,0.01616502058459446],[7,0.016049514815676957],[8,0.015170336118899286],[9,0.015576519363094121],[10,0.01490299345459789],[11,0.015126589918509126],[12,0.015850006137043238],[13,0.015123668825253844],[14,0.015384220983833075],[15,0.01610579394036904],[16,0.01637979206861928],[17,0.01619737717555836],[18,0.015365263679996133],[19,0.015952367859426886],[20,0.016095003520604223],[21,0.01646821858594194],[22,0.015387677471153438],[23,0.016529641288798302],[24,0.015287903079297394],[25,0.016856397967785597],[26,0.01672379410592839],[27,0.01577658182941377],[28,0.016363478964194655],[29,0.016341496724635363],[30,0.015957053226884454],[31,0.01633789559127763],[32,0.015688272251281887],[33,0.016318523441441357],[34,0.01602209178963676],[35,0.01623544399626553],[36,0.01594467827817425],[37,0.015589922084473073],[38,0.015975633345078677],[39,0.01513460004935041],[40,0.01619111152831465],[41,0.01620461995480582],[42,0.016414244717452675],[43,0.016518934804480523],[44,0.01615689683239907],[45,0.015800899651367217],[46,0.016716221696697176],[47,0.016262758406810462],[48,0.016544767015147954],[49,0.01649336627451703],[50,0.01659804763039574],[51,0.0169804286560975],[52,0.016434288234449923],[53,0.016217988857533783],[54,0.016727703507058322],[55,0.015984096738975495],[56,0.016124231973662972],[58,0.017021609121002257],[59,0.01679999742191285],[60,0.01678759272908792],[61,0.01616502460092306],[62,0.014967053895816207],[63,0.01672939321724698],[64,0.017183856281917542],[65,0.01664112036814913],[66,0.01615300861885771],[67,0.01716067997040227],[68,0.015827911265660077],[69,0.016389922646339983],[70,0.016279231756925583],[71,0.015545766509603709],[72,0.016549582942388952],[73,0.016542578057851642],[74,0.016069239762146026],[75,0.01654030941426754],[76,0.016862142307218164],[77,0.016727928130421788],[78,0.01696282037300989],[79,0.01704749691998586],[80,0.01655264780856669],[81,0.01666478719562292],[82,0.016020216513425112],[83,0.017135745962150395],[84,0.016710361931473017],[85,0.01644541247515008],[86,0.015909937908872962],[87,0.016872288659214973],[88,0.016676127910614014],[89,0.01680906437104568],[90,0.01565126469358802],[91,0.016157687176018953],[92,0.017024302971549332],[93,0.01684855535859242],[94,0.016013496671803296],[95,0.017158931703306735],[98,0.01718986447667703],[99,0.016385928902309388],[100,0.016633932769764215],[101,0.01588103489484638],[102,0.0169947009999305],[104,0.01677281962474808],[105,0.016959616099484265],[106,0.017686676466837525],[107,0.017240363347809762],[108,0.016270359454210848],[109,0.016751181858126074],[110,0.016628622950520366],[111,0.016358657856471837],[112,0.017512602207716554],[113,0.01730180490994826],[114,0.017188897414598614],[115,0.017095242103096098],[116,0.016869035025592893],[117,0.016642501403111964],[118,0.017533648817334324],[119,0.016824906168039888],[120,0.01702570082852617],[121,0.016659228364005685],[122,0.01723920105723664],[123,0.016811317531391978],[124,0.01744122593663633],[125,0.01760043081594631],[126,0.016951985249761492],[127,0.016996074991766363],[128,0.01760607148753479],[129,0.017264852940570563],[130,0.01759291219059378],[132,0.01640245970338583],[134,0.017071489652153105],[136,0.016642238246276975],[137,0.01670463097980246],[138,0.016744249616749585],[139,0.017222756054252386],[140,0.016873570159077644],[141,0.01777230022707954],[142,0.01683571795001626],[143,0.017129913030657917],[145,0.016807068488560617],[146,0.017224559793248773],[148,0.017674043192528188],[149,0.016632848361041397],[151,0.017253212921787053],[152,0.017682694597169757],[153,0.016560303862206638],[154,0.017243098351173103],[156,0.017067693930584937],[157,0.017267903371248394],[158,0.017443765536881983],[160,0.01751318317838013],[162,0.016633108665701002],[164,0.017536596162244678],[165,0.01758150855312124],[166,0.017316454148385674],[167,0.017494912550318986],[168,0.016769614536315203],[170,0.01726531208259985],[171,0.0173232433735393],[172,0.0171601987676695],[173,0.01753415778512135],[174,0.016623332223389298],[175,0.017054743599146605],[176,0.017190517100971192],[179,0.016849814797751606]],"confianca_pos_correcao":[[0,0.1587987095117569],[1,0.1663273274898529],[2,0.17912134528160095],[3,0.1631140410900116],[4,0.1701049655675888],[5,0.16917672753334045],[6,0.14841961860656738],[7,0.17517447471618652],[8,0.1600557416677475],[9,0.16052547097206116],[10,0.14431972801685333],[11,0.1633056402206421],[12,0.1525920331478119],[13,0.15926600992679596],[14,0.16420531272888184],[15,0.16214346885681152],[16,0.18704496324062347],[17,0.16519415378570557],[18,0.15611524879932404],[19,0.17925547063350677],[20,0.15833494067192078],[21,0.1518106460571289],[22,0.168626070022583],[23,0.1704416424036026],[24,0.16131165623664856],[25,0.18132872879505157],[26,0.18253028392791748],[27,0.14700543880462646],[28,0.15818580985069275],[29,0.17825765907764435],[30,0.1553850919008255],[31,0.1577756702899933],[32,0.14996713399887085],[33,0.1858246624469757],[34,0.14773237705230713],[35,0.14630520343780518],[36,0.14721813797950745],[37,0.1610732227563858],[38,0.1230509951710701],[39,0.14974986016750336],[40,0.16982029378414154],[41,0.16396604478359222],[42,0.16920259594917297],[43,0.1485423445701599],[44,0.16894589364528656],[45,0.14777806401252747],[46,0.16185319423675537],[47,0.17267826199531555],[48,0.16693347692489624],[49,0.18061144649982452],[50,0.15225984156131744],[51,0.18312600255012512],[52,0.16770082712173462],[53,0.14962968230247498],[54,0.17909981310367584],[55,0.16141952574253082],[56,0.17458635568618774],[58,0.17352823913097382],[59,0.16666799783706665],[60,0.18238267302513123],[61,0.16688455641269684],[62,0.13234135508537292],[63,0.18437083065509796],[64,0.17498065531253815],[65,0.18873770534992218],[66,0.1708756983280182],[67,0.17525817453861237],[68,0.1554616391658783],[69,0.17447271943092346],[70,0.1566036343574524],[71,0.16322262585163116],[72,0.17739078402519226],[73,0.16943442821502686],[74,0.161244198679924],[75,0.1959790587425232],[76,0.1794375628232956],[77,0.165488138794899],[78,0.17656467854976654],[79,0.16869157552719116],[80,0.16616564989089966],[81,0.18107832968235016],[82,0.17148537933826447],[83,0.18580180406570435],[84,0.17943456768989563],[85,0.1608690619468689],[86,0.1618165373802185],[87,0.18248027563095093],[88,0.19001397490501404],[89,0.15740828216075897],[90,0.15138156712055206],[91,0.1554187834262848],[92,0.1856810301542282],[93,0.17465457320213318],[94,0.1631956696510315],[95,0.17258146405220032],[98,0.17971810698509216],[99,0.16570588946342468],[100,0.1825055181980133],[101,0.16742363572120667],[102,0.1881554126739502],[104,0.1745765656232834],[105,0.1879185289144516],[106,0.18023289740085602],[107,0.18222127854824066],[108,0.1607009470462799],[109,0.17419284582138062],[110,0.17272350192070007],[111,0.1578918993473053],[112,0.1821414679288864],[113,0.1861209273338318],[114,0.18036682903766632],[115,0.18187958002090454],[116,0.19053493440151215],[117,0.1654602289199829],[118,0.19134759902954102],[119,0.16087384521961212],[120,0.18222679197788239],[121,0.1776803433895111],[122,0.1786060482263565],[123,0.1691613793373108],[124,0.19052045047283173],[125,0.19370625913143158],[126,0.17165188491344452],[127,0.17674757540225983],[128,0.19645051658153534],[129,0.20197993516921997],[130,0.20191556215286255],[132,0.18772654235363007],[134,0.18445192277431488],[136,0.17469234764575958],[137,0.1677069514989853],[138,0.16896817088127136],[139,0.17658907175064087],[140,0.1745469719171524],[141,0.19866693019866943],[142,0.18142516911029816],[143,0.175930455327034],[145,0.19531768560409546],[146,0.1821127086877823],[148,0.18900153040885925],[149,0.18137390911579132],[151,0.17204469442367554],[152,0.18441344797611237],[153,0.15157270431518555],[154,0.18329522013664246],[156,0.18380318582057953],[157,0.17717640101909637],[158,0.2028713822364807],[160,0.18476104736328125],[162,0.18993763625621796],[164,0.18922974169254303],[165,0.1929178386926651],[166,0.19783638417720795],[167,0.18783369660377502],[168,0.1825595498085022],[170,0.1676768958568573],[171,0.17470483481884003],[172,0.16972526907920837],[173,0.17687201499938965],[174,0.1784033328294754],[175,0.18470066785812378],[176,0.1751042753458023],[179,0.18028345704078674]],"ece_calibracao":[[0,0.11367493326542899]],"ece_pos_calibracao":[[0,0.04155296389944852]],"escore_aprendizado":[[0,0.0],[25,0.1875],[50,0.1875],[75,0.19861775566605808],[100,0.1789015181673504],[125,0.17339257615937123],[150,0.1955498766240361],[175,0.18667495139451393],[179,0.20192427652934322]],"lr":[[0,0.000011249999999999999],[1,0.000011249999999999999],[2,0.000011249999999999999],[3,0.000011249999999999999],[4,0.000011249999999999999],[5,0.000011249999999999999],[6,0.000011249999999999999],[7,0.000011249999999999999],[8,0.000011249999999999999],[9,0.000011249999999999999],[10,0.000011249999999999999],[11,0.000011249999999999999],[12,0.000011249999999999999],[13,0.000011249999999999999],[14,0.000011249999999999999],[15,0.000011249999999999999],[16,0.000011249999999999999],[17,0.000011249999999999999],[18,0.000011249999999999999],[19,0.000011249999999999999],[20,0.000011249999999999999],[21,0.000011249999999999999],[22,0.000011249999999999999],[23,0.000011249999999999999],[24,0.000011249999999999999],[25,0.000011249999999999999],[26,0.000011249999999999999],[27,0.000011249999999999999],[28,0.000011249999999999999],[29,0.000011249999999999999],[30,0.000011249999999999999],[31,0.000011249999999999999],[32,0.000011249999999999999],[33,0.000011249999999999999],[34,0.000011249999999999999],[35,0.000011249999999999999],[36,0.000011249999999999999],[37,0.000011249999999999999],[38,0.000011249999999999999],[39,0.000011249999999999999],[40,0.000011249999999999999],[41,0.000011249999999999999],[42,0.000011249999999999999],[43,0.000011249999999999999],[44,0.000011249999999999999],[45,0.000011249999999999999],[46,0.000011249999999999999],[47,0.000011249999999999999],[48,0.000011249999999999999],[49,0.000011249999999999999],[50,0.000011249999999999999],[51,0.000011249999999999999],[52,0.000011249999999999999],[53,0.000011249999999999999],[54,0.000011249999999999999],[55,0.000011249999999999999],[56,0.000011249999999999999],[57,0.000011249999999999999],[58,0.000011249999999999999],[59,0.000011249999999999999],[60,0.000011249999999999999],[61,0.000011249999999999999],[62,0.000011249999999999999],[63,0.000011249999999999999],[64,0.000011249999999999999],[65,0.000011249999999999999],[66,0.000011249999999999999],[67,0.000011249999999999999],[68,0.000011249999999999999],[69,0.000011249999999999999],[70,0.000011249999999999999],[71,0.000011249999999999999],[72,0.000011249999999999999],[73,0.000011249999999999999],[74,0.000011249999999999999],[75,0.000011249999999999999],[76,0.000011249999999999999],[77,0.000011249999999999999],[78,0.000011249999999999999],[79,0.000011249999999999999],[80,0.000011249999999999999],[81,0.000011249999999999999],[82,0.000011249999999999999],[83,0.000011249999999999999],[84,0.000011249999999999999],[85,0.000011249999999999999],[86,0.000011249999999999999],[87,0.000011249999999999999],[88,0.000011249999999999999],[89,0.000011249999999999999],[90,0.000011249999999999999],[91,0.000011249999999999999],[92,0.000011249999999999999],[93,0.000011249999999999999],[94,0.000011249999999999999],[95,0.000011249999999999999],[96,0.000011249999999999999],[97,0.000011249999999999999],[98,0.000011249999999999999],[99,0.000011249999999999999],[100,0.000011249999999999999],[101,0.000011249999999999999],[102,0.000011249999999999999],[103,0.000011249999999999999],[104,0.000011249999999999999],[105,0.000011249999999999999],[106,0.000011249999999999999],[107,0.000011249999999999999],[108,0.000011249999999999999],[109,0.000011249999999999999],[110,0.000011249999999999999],[111,0.000011249999999999999],[112,0.000011249999999999999],[113,0.000011249999999999999],[114,0.000011249999999999999],[115,0.000011249999999999999],[116,0.000011249999999999999],[117,0.000011249999999999999],[118,0.000011249999999999999],[119,0.000011249999999999999],[120,0.000011249999999999999],[121,0.000011249999999999999],[122,0.000011249999999999999],[123,0.000011249999999999999],[124,0.000011249999999999999],[125,0.000011249999999999999],[126,0.000011249999999999999],[127,0.000011249999999999999],[128,0.000011249999999999999],[129,0.000011249999999999999],[130,0.000011249999999999999],[131,0.000011249999999999999],[132,0.000011249999999999999],[133,0.000011249999999999999],[134,0.000011249999999999999],[135,0.000011249999999999999],[136,0.000011249999999999999],[137,0.000011249999999999999],[138,0.000011249999999999999],[139,0.000011249999999999999],[140,0.000011249999999999999],[141,0.000011249999999999999],[142,0.000011249999999999999],[143,0.000011249999999999999],[144,0.000011249999999999999],[145,0.000011249999999999999],[146,0.000011249999999999999],[147,0.000011249999999999999],[148,0.000011249999999999999],[149,0.000011249999999999999],[150,0.000011249999999999999],[151,0.000011249999999999999],[152,0.000011249999999999999],[153,0.000011249999999999999],[154,0.000011249999999999999],[155,0.000011249999999999999],[156,0.000011249999999999999],[157,0.000011249999999999999],[158,0.000011249999999999999],[159,0.000011249999999999999],[160,0.000011249999999999999],[161,0.000011249999999999999],[162,0.000011249999999999999],[163,0.000011249999999999999],[164,0.000011249999999999999],[165,0.000011249999999999999],[166,0.000011249999999999999],[167,0.000011249999999999999],[168,0.000011249999999999999],[169,0.000011249999999999999],[170,0.000011249999999999999],[171,0.000011249999999999999],[172,0.000011249999999999999],[173,0.000011249999999999999],[174,0.000011249999999999999],[175,0.000011249999999999999],[176,0.000011249999999999999],[177,0.000011249999999999999],[178,0.000011249999999999999],[179,0.000011249999999999999]],"norma_delta":[[0,1.5881213106234895],[1,1.5187163926162026],[2,1.4676972532519672],[3,1.5344224879730126],[4,1.5860104097791976],[5,1.433515000843958],[6,1.473765959390385],[7,1.478253884372159],[8,1.4521736394238383],[9,1.507312909310302],[10,1.4530303267945637],[11,1.420800274476463],[12,1.5223447352823842],[13,1.5231012217747875],[14,1.4506522849038557],[15,1.414737940538519],[16,1.4384710929440312],[17,1.5391225270604851],[18,1.4168698595220457],[19,1.357180040072414],[20,1.4439511873179134],[21,1.4591488419944938],[22,1.43338742967291],[23,1.3670427954253046],[24,1.414136473686119],[25,1.4153083666107067],[26,1.3862391542469625],[27,1.4801708716909903],[28,1.3233048878134017],[29,1.4592565157576403],[30,1.4188916274309595],[31,1.336727154346936],[32,1.3984532275460964],[33,1.3608432163378776],[34,1.394215224758686],[35,1.3234003739627276],[36,1.2936490392682327],[37,1.3195586450690584],[38,1.3527493420135583],[39,1.3094055845926666],[40,1.338821376960053],[41,1.3585567861993955],[42,1.3778088230733765],[43,1.3238328579626406],[44,1.347021865803189],[45,1.3914350657662604],[46,1.3155799785531048],[47,1.3162802344433508],[48,1.3699135768389963],[49,1.339731817880561],[50,1.2411241598580742],[51,1.2192444409698728],[52,1.279440835322008],[53,1.2082798003253752],[54,1.328700086960625],[55,1.294848685979613],[56,1.2721077933252138],[58,1.3517597914451],[59,1.247227741721065],[60,1.2230872713799126],[61,1.2796265621371403],[62,1.1947759783024399],[63,1.2206058554996286],[64,1.2175647645522645],[65,1.193042169796082],[66,1.2479505427140298],[67,1.173009399669089],[68,1.2320705100355331],[69,1.1712692729785104],[70,1.2830161505683877],[71,1.205887731200803],[72,1.2398159011953394],[73,1.1971719278882653],[74,1.2607118350274948],[75,1.2853924844322975],[76,1.1853782370471935],[77,1.1418602408053997],[78,1.182272649043498],[79,1.1762471844176303],[80,1.216092856626294],[81,1.2228879861322488],[82,1.2214267874507894],[83,1.1900000105785722],[84,1.14475130953592],[85,1.1374170839041227],[86,1.2302429960935586],[87,1.169551121932147],[88,1.2054623280401737],[89,1.166737497541043],[90,1.1434503339916178],[91,1.0915031242657909],[92,1.1990896149234676],[93,1.2426560196120255],[94,1.2983554524964347],[95,1.2455282333157174],[98,1.1934021288116785],[99,1.178914859201837],[100,1.067260679358217],[101,1.1616057001659732],[102,1.1513711219053855],[104,1.221638216066434],[105,1.1251509353264255],[106,1.1331374129947858],[107,1.2007797469537347],[108,1.113963563867668],[109,1.215493620218272],[110,1.2421799665498516],[111,1.1749033137372877],[112,1.0471369287567998],[113,1.0969081515005572],[114,1.1423371933143778],[115,1.0768091215505446],[116,1.1655558281795044],[117,1.1567673427913285],[118,1.0500327354959456],[119,1.0040927223954677],[120,1.02617605010288],[121,1.0883687747502586],[122,1.0257829188197465],[123,1.0835483533265058],[124,1.168828437335468],[125,1.1202796345975004],[126,1.1129780138865442],[127,1.155153373096526],[128,1.1215102322639294],[129,1.1001367202236771],[130,1.108218436392535],[132,1.063895823326342],[134,1.0782330223920904],[136,1.015893812551505],[137,1.0501463152349244],[138,1.0829516741114045],[139,1.1098491568784763],[140,1.0869647320147047],[141,1.1049514476759563],[142,1.045328791880555],[143,1.0375462992804874],[145,1.1107967024589718],[146,1.034538343733939],[148,1.0768360227858174],[149,1.0458201887563598],[151,1.0746379242532846],[152,1.0320736456919322],[153,1.0655785447004094],[154,1.1212779036742258],[156,1.0618331162677959],[157,0.9926745803608082],[158,1.0321711270000715],[160,1.1032074354455075],[162,1.1197309551488877],[164,1.047206427648706],[165,1.0202195339874658],[166,1.0668857600290191],[167,1.0997551233649665],[168,1.0428433003505362],[170,1.0519360857864473],[171,1.0549136449174559],[172,1.0122361204289128],[173,1.0879914324367008],[174,0.9915395107543743],[175,1.0664416855734016],[176,1.0151726522319844],[179,1.011296069836927]],"norma_grad":[[0,1.1408509894666303],[1,1.1620085168212773],[2,1.0717859918857777],[3,0.9599823276242924],[4,1.0849271631996633],[5,1.046068254589276],[6,1.0972726294357853],[7,1.1782997893302891],[8,1.1724296450318092],[9,1.1342181850315487],[10,1.1596105914821584],[11,1.074006801666198],[12,1.1719202932917543],[13,1.238410597309227],[14,1.1567173953444683],[15,1.103070143394678],[16,0.9881221960448122],[17,1.0863905702819072],[18,1.2623275458302878],[19,1.0427515851464686],[20,0.9654524585568002],[21,0.9980506381745176],[22,1.0998335411597937],[23,1.2273590471875544],[24,1.1606332471933027],[25,1.0623696710347608],[26,1.0013156268885286],[27,1.15433658746776],[28,1.123854479223399],[29,1.0937723963502561],[30,1.0128954737379696],[31,1.1563576577084211],[32,1.0139426177988846],[33,1.040041358207631],[34,1.0953961899153484],[35,1.0276038466230804],[36,1.024496688481309],[37,1.1889800061075806],[38,1.2001040837718178],[39,1.0557312557170926],[40,1.2209234432518836],[41,1.1937919035716358],[42,1.104659246071382],[43,1.1048959292112999],[44,1.0982722841840904],[45,1.1157871715892738],[46,1.0977537824335946],[47,1.110290963635313],[48,1.014946366624604],[49,1.0679475056858099],[50,1.0047684710359144],[51,1.0637268619064597],[52,1.0760724249998768],[53,1.001403844669581],[54,1.1438561122558717],[55,1.1558197340202172],[56,1.1181234275691552],[57,2.143114624452434],[58,1.0724950702992064],[59,1.1675023199846155],[60,1.1515349124592495],[61,1.0272396694426715],[62,1.2352242883822042],[63,1.1387031557667886],[64,1.131338193555846],[65,1.1483496680539762],[66,1.047181105024617],[67,1.2109860572179512],[68,1.0062536395018291],[69,1.1014797109416863],[70,1.1007108248764754],[71,0.9576348940925878],[72,0.9721146195377273],[73,1.2029629161538964],[74,1.1352208810595206],[75,1.058138448172156],[76,1.0807239480178379],[77,1.02457448656393],[78,1.0726404156249443],[79,1.0702693541472654],[80,1.1489117154524593],[81,1.108072070426233],[82,1.1948931636625613],[83,1.1329279340076943],[84,1.0028653517409813],[85,1.1461768915597113],[86,1.1229880229845943],[87,1.0087857959782998],[88,1.0623247328279406],[89,0.9501125211764239],[90,1.0020594186251126],[91,1.0429475143796836],[92,1.135989304323665],[93,1.0721633595546796],[94,1.0547363265752603],[95,1.088800986610709],[96,2.2669286304172647],[97,2.3954033061071267],[98,1.1269234633961163],[99,1.0464108464605488],[100,1.0946585133188407],[101,1.0888350009896808],[102,1.0130571306772471],[103,2.0116712784948527],[104,1.0511956912906073],[105,1.091401599037621],[106,1.1245986167767974],[107,1.1410530765989884],[108,1.1036604814427728],[109,1.0111020790272762],[110,1.0800145915131298],[111,1.162890173030024],[112,0.9821830167750351],[113,1.0058008861239327],[114,1.0196685546693574],[115,1.0753382433189824],[116,1.172967399678557],[117,1.1099166653311547],[118,1.1027899485893229],[119,1.0744694652351066],[120,0.8888696102338669],[121,1.1156725462454988],[122,1.1216605598456653],[123,1.0606588953519689],[124,1.1018656325613787],[125,1.1877490977438965],[126,1.1378804564481921],[127,1.0914884564719853],[128,1.0313243143364672],[129,1.0950742031446645],[130,1.183261649867716],[131,1.974679591352598],[132,1.0875035660116001],[133,2.210752788121471],[134,1.1586115612938919],[135,1.8156598612687298],[136,1.0338708977376527],[137,1.1832502899848218],[138,1.0234304195054762],[139,1.0233816091253474],[140,1.083521231628433],[141,1.205081793516156],[142,1.1658930620288566],[143,1.11549829118145],[144,2.30350570372614],[145,1.3243929726231503],[146,1.0508834821378938],[147,2.0364198623930747],[148,1.1383850067557006],[149,1.0342079584192119],[150,1.7307760130679946],[151,1.1765056311177946],[152,1.0343096807189474],[153,1.18133846184018],[154,0.9634770545039816],[155,2.0937149093788983],[156,1.1582335618733566],[157,1.059747661317403],[158,1.2236411389983588],[159,1.958231556905546],[160,1.041767084559792],[161,2.0395926403140874],[162,1.1087352637330974],[163,2.04896552222598],[164,1.1238861217276406],[165,1.2499079725695277],[166,1.0911500003634453],[167,0.9467151839924393],[168,1.178811262782175],[169,2.177803870259314],[170,1.1256682955836608],[171,1.0670686738439257],[172,1.1159536174505167],[173,1.1515091290804427],[174,1.0784027666725535],[175,1.0988231519956964],[176,1.1095239628253828],[177,2.346264572125264],[178,2.00156131768225],[179,1.123615076102498]],"perda":[[0,3.9194343090057373],[1,4.087978363037109],[2,4.064610481262207],[3,3.9004688262939453],[4,3.937434196472168],[5,3.9801411628723145],[6,3.926401138305664],[7,4.046908378601074],[8,4.003106594085693],[9,4.005749225616455],[10,3.96597957611084],[11,3.932399272918701],[12,4.0554423332214355],[13,3.929011344909668],[14,4.005979537963867],[15,3.8883581161499023],[16,4.0282487869262695],[17,3.9987754821777344],[18,3.846325397491455],[19,4.034093379974365],[20,4.117684364318848],[21,3.998878002166748],[22,4.009410381317139],[23,3.9910614490509033],[24,4.081048011779785],[25,3.914362668991089],[26,4.144887447357178],[27,4.01850700378418],[28,4.124208450317383],[29,4.068880081176758],[30,3.9478023052215576],[31,3.958327293395996],[32,4.126319885253906],[33,3.8923895359039307],[34,3.914989948272705],[35,3.9405405521392822],[36,4.000115871429443],[37,3.901688814163208],[38,4.082619667053223],[39,4.0550665855407715],[40,4.009676456451416],[41,4.000525951385498],[42,3.904207229614258],[43,4.021977424621582],[44,4.004317283630371],[45,3.9284579753875732],[46,3.9724302291870117],[47,3.947613000869751],[48,4.022891044616699],[49,4.069355010986328],[50,3.942828893661499],[51,3.901921033859253],[52,3.9620864391326904],[53,3.844869613647461],[54,3.9835174083709717],[55,4.0107340812683105],[56,3.86012864112854],[57,3.9929594652993337],[58,3.891345977783203],[59,4.1436614990234375],[60,3.9580047130584717],[61,3.925525665283203],[62,3.8363969326019287],[63,3.8033995628356934],[64,3.9778149127960205],[65,4.09800910949707],[66,3.9001619815826416],[67,3.985206127166748],[68,3.966222047805786],[69,3.8126461505889893],[70,4.110163688659668],[71,3.93788480758667],[72,3.9434359073638916],[73,3.8149378299713135],[74,3.981889247894287],[75,3.910994052886963],[76,3.926023483276367],[77,4.031162738800049],[78,3.9504055976867676],[79,3.9473400115966797],[80,3.932459831237793],[81,3.757617473602295],[82,3.9626693725585938],[83,3.9926092624664307],[84,3.997727870941162],[85,3.930081844329834],[86,4.022006511688232],[87,4.031554698944092],[88,3.9477455615997314],[89,4.051133155822754],[90,3.9200427532196045],[91,3.995992660522461],[92,3.885881185531616],[93,4.14095401763916],[94,4.06342077255249],[95,4.0369157791137695],[96,4.053732088633946],[97,4.033645868301392],[98,4.02833890914917],[99,3.929987907409668],[100,4.058657646179199],[101,3.9100680351257324],[102,4.044567108154297],[103,3.933227675301688],[104,3.8929080963134766],[105,3.981990337371826],[106,4.086297512054443],[107,4.002747535705566],[108,3.890859603881836],[109,3.9656243324279785],[110,3.8666393756866455],[111,4.056665897369385],[112,3.969169855117798],[113,3.9328463077545166],[114,3.889716148376465],[115,4.07361364364624],[116,4.1161088943481445],[117,3.9725546836853027],[118,3.9570634365081787],[119,3.971015214920044],[120,3.9546022415161133],[121,4.1244964599609375],[122,4.082955837249756],[123,3.9142136573791504],[124,3.879279136657715],[125,3.9412806034088135],[126,3.9267473220825195],[127,4.085089683532715],[128,4.018453121185303],[129,4.126855850219727],[130,3.954861879348755],[131,3.998305218560355],[132,4.016144752502441],[133,4.098225695746286],[134,4.083544731140137],[135,4.037397589002337],[136,3.9111850261688232],[137,4.0095295906066895],[138,3.8264782428741455],[139,3.9539082050323486],[140,3.8899524211883545],[141,4.080811023712158],[142,3.980506658554077],[143,3.968421459197998],[144,3.8352764674595425],[145,3.8185315132141113],[146,3.9458961486816406],[147,3.9066452980041504],[148,3.8366189002990723],[149,3.867034912109375],[150,4.05700877734593],[151,3.8473520278930664],[152,4.081768035888672],[153,3.998035430908203],[154,3.8764076232910156],[155,3.997589315686907],[156,3.935149669647217],[157,4.169554233551025],[158,3.9677000045776367],[159,3.983267000743321],[160,4.053371906280518],[161,4.014926195144653],[162,3.933337450027466],[163,3.8815345764160156],[164,3.9069101810455322],[165,3.8870015144348145],[166,3.9478001594543457],[167,4.013628959655762],[168,3.9438586235046387],[169,4.112010206495013],[170,3.9096741676330566],[171,3.909297466278076],[172,3.896806478500366],[173,4.234826564788818],[174,3.8643367290496826],[175,3.9398603439331055],[176,4.009671211242676],[177,3.8983241489955356],[178,3.9342243671417236],[179,4.10706901550293]],"taxa_gatilho":[[0,0.75],[1,0.75],[2,0.5],[3,1.0],[4,0.75],[5,0.75],[6,0.75],[7,1.0],[8,1.0],[9,0.75],[10,1.0],[11,0.75],[12,0.75],[13,0.75],[14,0.75],[15,0.5],[16,0.25],[17,0.75],[18,0.75],[19,0.75],[20,0.5],[21,0.5],[22,1.0],[23,0.25],[24,1.0],[25,0.25],[26,0.5],[27,0.75],[28,0.5],[29,0.5],[30,0.5],[31,0.5],[32,0.75],[33,0.75],[34,0.75],[35,0.75],[36,0.75],[37,1.0],[38,1.0],[39,1.0],[40,0.75],[41,0.5],[42,0.5],[43,0.75],[44,0.5],[45,0.75],[46,0.5],[47,0.5],[48,0.25],[49,0.5],[50,0.5],[51,0.5],[52,0.75],[53,0.75],[54,0.5],[55,0.75],[56,0.75],[57,0.0],[58,0.25],[59,0.25],[60,0.25],[61,0.75],[62,1.0],[63,0.25],[64,0.25],[65,0.5],[66,0.75],[67,0.25],[68,0.75],[69,0.5],[70,0.75],[71,1.0],[72,0.25],[73,0.75],[74,1.0],[75,0.5],[76,0.25],[77,0.5],[78,0.5],[79,0.25],[80,0.5],[81,0.75],[82,0.75],[83,0.25],[84,0.5],[85,0.75],[86,1.0],[87,0.25],[88,0.5],[89,0.5],[90,1.0],[91,1.0],[92,0.25],[93,0.5],[94,1.0],[95,0.5],[96,0.0],[97,0.0],[98,0.25],[99,0.5],[100,0.75],[101,0.75],[102,0.25],[103,0.0],[104,0.25],[105,0.5],[106,0.5],[107,0.25],[108,0.75],[109,0.5],[110,0.5],[111,0.75],[112,0.25],[113,0.25],[114,0.25],[115,0.5],[116,0.25],[117,0.75],[118,0.5],[119,0.75],[120,0.5],[121,0.75],[122,0.5],[123,0.5],[124,0.25],[125,0.25],[126,0.75],[127,0.5],[128,0.25],[129,0.5],[130,0.25],[131,0.0],[132,0.75],[133,0.0],[134,0.25],[135,0.0],[136,0.75],[137,0.5],[138,0.75],[139,0.25],[140,0.5],[141,0.25],[142,0.5],[143,0.25],[144,0.0],[145,0.5],[146,0.5],[147,0.0],[148,0.5],[149,0.5],[150,0.0],[151,0.75],[152,0.5],[153,1.0],[154,0.25],[155,0.0],[156,0.25],[157,0.25],[158,0.25],[159,0.0],[160,0.25],[161,0.0],[162,0.5],[163,0.0],[164,0.25],[165,0.25],[166,0.75],[167,0.25],[168,0.5],[169,0.0],[170,0.5],[171,0.25],[172,0.5],[173,0.25],[174,0.5],[175,0.25],[176,0.25],[177,0.0],[178,0.0],[179,0.5]],"temperatura_calibrada":[[0,0.28844055695274645]]}}
|
|
|
|
| 1 |
{"tipo":"ajuste","dados":{"passo":0,"parametro":"fase_c_base","de":0.0,"para":1.0,"motivo":"carregado checkpoint da fase B: /home/z/my-project/khtst-rust/resultados/gerador_conceitos.json (ajuste fino, não do zero)"}}
|
| 2 |
+
{"tipo":"ajuste","dados":{"passo":0,"parametro":"pensamento_tau_c","de":0.3,"para":0.023297131061553955,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, calibração inicial fase C)"}}
|
| 3 |
+
{"tipo":"ajuste","dados":{"passo":0,"parametro":"pensamento_tau_c","de":0.023297131061553955,"para":0.022317171096801758,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=1024)"}}
|
| 4 |
+
{"tipo":"ajuste","dados":{"passo":1,"parametro":"pensamento_tau_c","de":0.022317171096801758,"para":0.02209264039993286,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=2048)"}}
|
| 5 |
+
{"tipo":"ajuste","dados":{"passo":2,"parametro":"pensamento_tau_c","de":0.02209264039993286,"para":0.022000014781951904,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=3072)"}}
|
| 6 |
+
{"tipo":"ajuste","dados":{"passo":3,"parametro":"pensamento_tau_c","de":0.022000014781951904,"para":0.022239625453948975,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=4096)"}}
|
| 7 |
+
{"tipo":"ajuste","dados":{"passo":4,"parametro":"pensamento_tau_c","de":0.022239625453948975,"para":0.022184014320373535,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=5120)"}}
|
| 8 |
+
{"tipo":"ajuste","dados":{"passo":5,"parametro":"pensamento_tau_c","de":0.022184014320373535,"para":0.022012710571289062,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=6144)"}}
|
| 9 |
+
{"tipo":"ajuste","dados":{"passo":6,"parametro":"pensamento_tau_c","de":0.022012710571289062,"para":0.02229410409927368,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=7168)"}}
|
| 10 |
+
{"tipo":"ajuste","dados":{"passo":7,"parametro":"pensamento_tau_c","de":0.02229410409927368,"para":0.022495806217193604,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=8192)"}}
|
| 11 |
+
{"tipo":"ajuste","dados":{"passo":8,"parametro":"pensamento_tau_c","de":0.022495806217193604,"para":0.02274608612060547,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=9216)"}}
|
| 12 |
+
{"tipo":"ajuste","dados":{"passo":9,"parametro":"pensamento_tau_c","de":0.02274608612060547,"para":0.022810757160186768,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=10240)"}}
|
| 13 |
+
{"tipo":"ajuste","dados":{"passo":10,"parametro":"pensamento_tau_c","de":0.022810757160186768,"para":0.022719264030456543,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=11264)"}}
|
| 14 |
+
{"tipo":"ajuste","dados":{"passo":11,"parametro":"pensamento_tau_c","de":0.022719264030456543,"para":0.022697925567626953,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=12288)"}}
|
| 15 |
+
{"tipo":"ajuste","dados":{"passo":12,"parametro":"pensamento_tau_c","de":0.022697925567626953,"para":0.02268761396408081,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=13312)"}}
|
| 16 |
+
{"tipo":"ajuste","dados":{"passo":13,"parametro":"pensamento_tau_c","de":0.02268761396408081,"para":0.02280670404434204,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=14336)"}}
|
| 17 |
+
{"tipo":"ajuste","dados":{"passo":14,"parametro":"pensamento_tau_c","de":0.02280670404434204,"para":0.022728443145751953,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=15360)"}}
|
| 18 |
+
{"tipo":"ajuste","dados":{"passo":15,"parametro":"pensamento_tau_c","de":0.022728443145751953,"para":0.022736310958862305,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=16384)"}}
|
| 19 |
+
{"tipo":"ajuste","dados":{"passo":15,"parametro":"pensamento_n_tentativas","de":4.0,"para":8.0,"motivo":"A-3 Hoeffding+acordo (â=0.525 ⇒ p̂=0.611, δ=0.05)"}}
|
| 20 |
+
{"tipo":"ajuste","dados":{"passo":16,"parametro":"pensamento_tau_c","de":0.022736310958862305,"para":0.02274996042251587,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=17408)"}}
|
| 21 |
+
{"tipo":"ajuste","dados":{"passo":17,"parametro":"pensamento_tau_c","de":0.02274996042251587,"para":0.022856473922729492,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=18432)"}}
|
| 22 |
+
{"tipo":"ajuste","dados":{"passo":18,"parametro":"pensamento_tau_c","de":0.022856473922729492,"para":0.02278655767440796,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=19456)"}}
|
| 23 |
+
{"tipo":"ajuste","dados":{"passo":19,"parametro":"pensamento_tau_c","de":0.02278655767440796,"para":0.022822678089141846,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=20480)"}}
|
| 24 |
+
{"tipo":"ajuste","dados":{"passo":20,"parametro":"pensamento_tau_c","de":0.022822678089141846,"para":0.022863924503326416,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=21504)"}}
|
| 25 |
+
{"tipo":"ajuste","dados":{"passo":21,"parametro":"pensamento_tau_c","de":0.022863924503326416,"para":0.022868633270263672,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=22528)"}}
|
| 26 |
+
{"tipo":"ajuste","dados":{"passo":22,"parametro":"pensamento_tau_c","de":0.022868633270263672,"para":0.022899150848388672,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=23552)"}}
|
| 27 |
+
{"tipo":"ajuste","dados":{"passo":23,"parametro":"pensamento_tau_c","de":0.022899150848388672,"para":0.02296435832977295,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=24576)"}}
|
| 28 |
+
{"tipo":"ajuste","dados":{"passo":24,"parametro":"pensamento_tau_c","de":0.02296435832977295,"para":0.022948086261749268,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=25600)"}}
|
| 29 |
+
{"tipo":"ajuste","dados":{"passo":25,"parametro":"pensamento_tau_c","de":0.022948086261749268,"para":0.022920548915863037,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=26624)"}}
|
| 30 |
+
{"tipo":"ajuste","dados":{"passo":26,"parametro":"pensamento_tau_c","de":0.022920548915863037,"para":0.02294844388961792,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=27648)"}}
|
| 31 |
+
{"tipo":"ajuste","dados":{"passo":27,"parametro":"pensamento_tau_c","de":0.02294844388961792,"para":0.022922873497009277,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=28672)"}}
|
| 32 |
+
{"tipo":"ajuste","dados":{"passo":28,"parametro":"pensamento_tau_c","de":0.022922873497009277,"para":0.022958874702453613,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=29696)"}}
|
| 33 |
+
{"tipo":"ajuste","dados":{"passo":29,"parametro":"pensamento_tau_c","de":0.022958874702453613,"para":0.022955000400543213,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=30720)"}}
|
| 34 |
+
{"tipo":"ajuste","dados":{"passo":30,"parametro":"pensamento_tau_c","de":0.022955000400543213,"para":0.02293342351913452,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=31744)"}}
|
| 35 |
+
{"tipo":"ajuste","dados":{"passo":31,"parametro":"pensamento_tau_c","de":0.02293342351913452,"para":0.02293938398361206,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=32768)"}}
|
| 36 |
+
{"tipo":"ajuste","dados":{"passo":32,"parametro":"pensamento_tau_c","de":0.02293938398361206,"para":0.0229642391204834,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=33792)"}}
|
| 37 |
+
{"tipo":"ajuste","dados":{"passo":33,"parametro":"pensamento_tau_c","de":0.0229642391204834,"para":0.022974014282226562,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=34816)"}}
|
| 38 |
+
{"tipo":"ajuste","dados":{"passo":34,"parametro":"pensamento_tau_c","de":0.022974014282226562,"para":0.022957980632781982,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=35840)"}}
|
| 39 |
+
{"tipo":"ajuste","dados":{"passo":35,"parametro":"pensamento_tau_c","de":0.022957980632781982,"para":0.02293217182159424,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=36864)"}}
|
| 40 |
+
{"tipo":"ajuste","dados":{"passo":36,"parametro":"pensamento_tau_c","de":0.02293217182159424,"para":0.022953391075134277,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=37888)"}}
|
| 41 |
+
{"tipo":"ajuste","dados":{"passo":37,"parametro":"pensamento_tau_c","de":0.022953391075134277,"para":0.022935807704925537,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=38912)"}}
|
| 42 |
+
{"tipo":"ajuste","dados":{"passo":38,"parametro":"pensamento_tau_c","de":0.022935807704925537,"para":0.022968053817749023,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=39936)"}}
|
| 43 |
+
{"tipo":"ajuste","dados":{"passo":39,"parametro":"pensamento_tau_c","de":0.022968053817749023,"para":0.0229414701461792,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=40960)"}}
|
| 44 |
+
{"tipo":"ajuste","dados":{"passo":40,"parametro":"pensamento_tau_c","de":0.0229414701461792,"para":0.022938907146453857,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=41984)"}}
|
| 45 |
+
{"tipo":"ajuste","dados":{"passo":41,"parametro":"pensamento_tau_c","de":0.022938907146453857,"para":0.022935807704925537,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=43008)"}}
|
| 46 |
+
{"tipo":"ajuste","dados":{"passo":44,"parametro":"pensamento_tau_c","de":0.022935807704925537,"para":0.022957980632781982,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=45056)"}}
|
| 47 |
+
{"tipo":"ajuste","dados":{"passo":45,"parametro":"pensamento_tau_c","de":0.022957980632781982,"para":0.022972285747528076,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=46080)"}}
|
| 48 |
+
{"tipo":"ajuste","dados":{"passo":46,"parametro":"pensamento_tau_c","de":0.022972285747528076,"para":0.022983551025390625,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=47104)"}}
|
| 49 |
+
{"tipo":"ajuste","dados":{"passo":47,"parametro":"pensamento_tau_c","de":0.022983551025390625,"para":0.02296394109725952,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=48128)"}}
|
| 50 |
+
{"tipo":"ajuste","dados":{"passo":48,"parametro":"pensamento_tau_c","de":0.02296394109725952,"para":0.022973179817199707,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=49152)"}}
|
| 51 |
+
{"tipo":"ajuste","dados":{"passo":49,"parametro":"pensamento_tau_c","de":0.022973179817199707,"para":0.02298593521118164,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=50176)"}}
|
| 52 |
+
{"tipo":"ajuste","dados":{"passo":50,"parametro":"pensamento_tau_c","de":0.02298593521118164,"para":0.023021340370178223,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=51200)"}}
|
| 53 |
+
{"tipo":"ajuste","dados":{"passo":51,"parametro":"pensamento_tau_c","de":0.023021340370178223,"para":0.02301102876663208,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=52224)"}}
|
| 54 |
+
{"tipo":"ajuste","dados":{"passo":52,"parametro":"pensamento_tau_c","de":0.02301102876663208,"para":0.023013651371002197,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=53248)"}}
|
| 55 |
+
{"tipo":"ajuste","dados":{"passo":53,"parametro":"pensamento_tau_c","de":0.023013651371002197,"para":0.023046553134918213,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=54272)"}}
|
| 56 |
+
{"tipo":"ajuste","dados":{"passo":54,"parametro":"pensamento_tau_c","de":0.023046553134918213,"para":0.02307361364364624,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=55296)"}}
|
| 57 |
+
{"tipo":"ajuste","dados":{"passo":55,"parametro":"pensamento_tau_c","de":0.02307361364364624,"para":0.023107469081878662,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=56320)"}}
|
| 58 |
+
{"tipo":"ajuste","dados":{"passo":56,"parametro":"pensamento_tau_c","de":0.023107469081878662,"para":0.023133397102355957,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=57344)"}}
|
| 59 |
+
{"tipo":"ajuste","dados":{"passo":57,"parametro":"pensamento_tau_c","de":0.023133397102355957,"para":0.02313852310180664,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=58368)"}}
|
| 60 |
+
{"tipo":"ajuste","dados":{"passo":58,"parametro":"pensamento_tau_c","de":0.02313852310180664,"para":0.02313053607940674,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=59392)"}}
|
| 61 |
+
{"tipo":"ajuste","dados":{"passo":59,"parametro":"pensamento_tau_c","de":0.02313053607940674,"para":0.02314138412475586,"motivo":"A-1 quantil DKW 1−ε (ε=0.30, n=60416)"}}
|
| 62 |
+
{"tipo":"dump","dados":{"acordo_tentativas":[[0,0.5242253183229574],[1,0.5196614126422597],[2,0.5257449183457842],[3,0.5330622514350883],[4,0.5245019973954186],[5,0.5199602458160371],[6,0.5177712816318186],[7,0.5244399208507579],[8,0.53681094980008],[9,0.528316649433691],[10,0.5334040453502288],[11,0.5177172637389352],[12,0.5200667596169902],[13,0.5307594829549392],[14,0.515374661918031],[15,0.5255790033843368],[16,0.5281660798257993],[17,0.5346757676452398],[18,0.529241313051898],[19,0.5216506658119519],[20,0.5356716537013805],[21,0.5246350576829476],[22,0.5109627895775096],[23,0.5086547085278047],[24,0.5234480652385779],[25,0.5413357143406756],[26,0.5206464085786138],[27,0.5137109587279459],[28,0.5266877379441818],[29,0.5037831310764886],[30,0.5130414182446353],[31,0.5007295610848814],[32,0.5025985161191784],[33,0.5151924522748837],[34,0.5123762060732892],[35,0.5290023916689582],[36,0.5306032927765045],[37,0.5313207440291686],[38,0.5243620008792883],[39,0.5199399796244203],[40,0.5348069277048731],[41,0.5232541509564422],[42,0.5242976913577877],[44,0.5210769070157161],[45,0.519067988692162],[46,0.5042248214255475],[47,0.5024980471353047],[48,0.5083159557034378],[49,0.49144882046190713],[50,0.49601653466622037],[51,0.514461044940011],[52,0.5160899722638229],[53,0.5106741597604317],[54,0.5095089419725506],[55,0.5310550890959954],[56,0.5084244040869331],[57,0.5219405006306866],[58,0.5177929324951643],[59,0.5085814635191733]],"ce_gatilhada_com":[[0,4.0160642464955645],[1,4.020796358585358],[2,3.756197532018026],[3,3.972007393836975],[4,3.9106242656707764],[5,4.150133311748505],[6,4.096499919891357],[7,4.274447917938232],[8,4.214584827423096],[9,4.262876033782959],[10,3.9690253734588623],[11,4.19330507516861],[12,3.944504658381144],[13,4.155826568603516],[14,4.011726260185242],[15,4.138347466786702],[16,4.154810269673665],[17,3.807436466217041],[18,4.160148203372955],[19,3.9540923833847046],[20,4.257730007171631],[21,4.238237142562866],[22,4.117620388666789],[23,3.9574949741363525],[24,4.017991900444031],[25,4.146730422973633],[26,3.888241410255432],[27,4.000128924846649],[28,4.193357229232788],[29,3.8961690266927085],[30,3.9902414679527283],[31,4.162360827128093],[32,3.8762173652648926],[33,4.093463182449341],[34,4.1116257309913635],[35,4.110910892486572],[36,4.008694291114807],[37,3.9714513421058655],[38,4.276417016983032],[39,4.10725474357605],[40,4.390232880910237],[41,4.015871445337932],[42,3.932928959528605],[44,4.15267539024353],[45,3.9569873809814453],[46,3.8472858667373657],[47,3.852354347705841],[48,4.1043301820755005],[49,3.7693896293640137],[50,4.248522758483887],[51,3.7071454524993896],[52,4.210555076599121],[53,3.9695053100585938],[54,3.852332830429077],[55,3.6260454654693604],[56,3.816598415374756],[57,4.208217620849609],[58,4.0796865820884705],[59,4.101994673411052]],"ce_gatilhada_sem":[[0,4.0117941697438555],[1,3.995416045188904],[2,3.7062788804372153],[3,3.84058153629303],[4,3.7657753626505532],[5,4.03393679857254],[6,3.9927070140838623],[7,4.063235282897949],[8,3.8145322799682617],[9,4.33061957359314],[10,3.950933873653412],[11,4.194869160652161],[12,3.7901836236317954],[13,4.3861308097839355],[14,4.058150947093964],[15,4.249199628829956],[16,4.110453049341838],[17,3.9060821533203125],[18,4.134184658527374],[19,4.023251533508301],[20,4.20383882522583],[21,4.255165100097656],[22,3.9953749974568686],[23,3.984513282775879],[24,4.0208006501197815],[25,4.111786961555481],[26,3.871859312057495],[27,3.934393525123596],[28,4.285910367965698],[29,3.9298880894978843],[30,3.8995943665504456],[31,4.039295355478923],[32,3.9530187845230103],[33,3.9978226025899253],[34,4.031887412071228],[35,4.081463932991028],[36,3.8962252140045166],[37,3.949426531791687],[38,4.332481145858765],[39,4.124578356742859],[40,4.440631866455078],[41,3.9684581756591797],[42,3.9447473684946694],[44,4.1577277183532715],[45,3.92374324798584],[46,3.8568601608276367],[47,3.8175843358039856],[48,4.2266587018966675],[49,3.7471953630447388],[50,4.252926349639893],[51,3.749689261118571],[52,4.226971626281738],[53,3.9665422439575195],[54,3.747084140777588],[55,3.7225663661956787],[56,3.9323489665985107],[57,4.2901880741119385],[58,3.982586443424225],[59,4.114598751068115]],"confianca_media":[[0,0.02186690643429756],[1,0.021017425693571568],[2,0.02082684839842841],[3,0.021842343965545297],[4,0.02088458026992157],[5,0.020591802429407835],[6,0.022932012856472284],[7,0.023044777917675674],[8,0.023245058546308428],[9,0.0226029553450644],[10,0.021609652845654637],[11,0.021980683552101254],[12,0.0219064379343763],[13,0.023295592865906656],[14,0.02128947019809857],[15,0.021617707738187164],[16,0.023045436129905283],[17,0.023076636833138764],[18,0.020934443338774145],[19,0.022478081053122878],[20,0.022667937504593283],[21,0.02234623732510954],[22,0.022548168257344514],[23,0.023718831303995103],[24,0.022222056693863124],[25,0.021419211465399712],[26,0.022575483773835003],[27,0.021465268975589424],[28,0.022910094063263386],[29,0.02260238426970318],[30,0.021427035913802683],[31,0.02247519086813554],[32,0.022951125807594508],[33,0.02258136053569615],[34,0.022011240536812693],[35,0.021383841813076288],[36,0.022932822641450912],[37,0.021707330888602883],[38,0.023097841010894626],[39,0.021232965518720448],[40,0.022394207073375583],[41,0.022331446642056108],[42,0.022094753803685308],[44,0.02276808669557795],[45,0.022971999074798077],[46,0.02288906357716769],[47,0.02113351714797318],[48,0.022594301495701075],[49,0.022722536872606725],[50,0.023730107757728547],[51,0.021566612529568374],[52,0.022618462098762393],[53,0.02376235369592905],[54,0.023747722792904824],[55,0.023528666759375483],[56,0.023475029156543314],[57,0.02266909193713218],[58,0.0223821074469015],[59,0.02299303957261145]],"confianca_pos_correcao":[[0,0.017170101404190063],[1,0.015070827677845955],[2,0.016885248944163322],[3,0.018374895676970482],[4,0.016166063025593758],[5,0.015499933622777462],[6,0.021062828600406647],[7,0.0222613662481308],[8,0.021202150732278824],[9,0.01856091246008873],[10,0.013959418050944805],[11,0.014601214788854122],[12,0.016377698630094528],[13,0.021738233044743538],[14,0.01579440012574196],[15,0.016733000054955482],[16,0.017757147550582886],[17,0.021137062460184097],[18,0.013563212007284164],[19,0.01942417584359646],[20,0.018538599833846092],[21,0.012492356821894646],[22,0.017579518258571625],[23,0.02080530859529972],[24,0.013715420849621296],[25,0.015828007832169533],[26,0.017482515424489975],[27,0.013775487430393696],[28,0.018798530101776123],[29,0.018152408301830292],[30,0.014677587896585464],[31,0.019018039107322693],[32,0.01919858530163765],[33,0.01738257147371769],[34,0.0145476208999753],[35,0.01449913065880537],[36,0.01974528841674328],[37,0.014843683689832687],[38,0.019679786637425423],[39,0.015322884544730186],[40,0.017374254763126373],[41,0.01720760390162468],[42,0.018117297440767288],[44,0.019764592871069908],[45,0.018987078219652176],[46,0.01972634717822075],[47,0.015316982753574848],[48,0.0204097181558609],[49,0.01950886659324169],[50,0.02247670292854309],[51,0.01857130043208599],[52,0.018995223566889763],[53,0.02270491048693657],[54,0.02078261226415634],[55,0.02232854627072811],[56,0.022130470722913742],[57,0.020434889942407608],[58,0.014451826922595501],[59,0.02006591483950615]],"ece_calibracao":[[0,0.001855327282100916]],"ece_pos_calibracao":[[0,0.001855327282100916]],"escore_aprendizado":[[0,0.0],[25,0.1875],[50,0.1875],[59,0.1875]],"lr":[[0,0.000011249999999999999],[1,0.000011249999999999999],[2,0.000011249999999999999],[3,0.000011249999999999999],[4,0.000011249999999999999],[5,0.000011249999999999999],[6,0.000011249999999999999],[7,0.000011249999999999999],[8,0.000011249999999999999],[9,0.000011249999999999999],[10,0.000011249999999999999],[11,0.000011249999999999999],[12,0.000011249999999999999],[13,0.000011249999999999999],[14,0.000011249999999999999],[15,0.000011249999999999999],[16,0.000011249999999999999],[17,0.000011249999999999999],[18,0.000011249999999999999],[19,0.000011249999999999999],[20,0.000011249999999999999],[21,0.000011249999999999999],[22,0.000011249999999999999],[23,0.000011249999999999999],[24,0.000011249999999999999],[25,0.000011249999999999999],[26,0.000011249999999999999],[27,0.000011249999999999999],[28,0.000011249999999999999],[29,0.000011249999999999999],[30,0.000011249999999999999],[31,0.000011249999999999999],[32,0.000011249999999999999],[33,0.000011249999999999999],[34,0.000011249999999999999],[35,0.000011249999999999999],[36,0.000011249999999999999],[37,0.000011249999999999999],[38,0.000011249999999999999],[39,0.000011249999999999999],[40,0.000011249999999999999],[41,0.000011249999999999999],[42,0.000011249999999999999],[43,0.000011249999999999999],[44,0.000011249999999999999],[45,0.000011249999999999999],[46,0.000011249999999999999],[47,0.000011249999999999999],[48,0.000011249999999999999],[49,0.000011249999999999999],[50,0.000011249999999999999],[51,0.000011249999999999999],[52,0.000011249999999999999],[53,0.000011249999999999999],[54,0.000011249999999999999],[55,0.000011249999999999999],[56,0.000011249999999999999],[57,0.000011249999999999999],[58,0.000011249999999999999],[59,0.000011249999999999999]],"norma_delta":[[0,1.5482000205413142],[1,1.6599711954536192],[2,1.5909539173053178],[3,1.675607881270046],[4,1.777453207481186],[5,1.7457843139370264],[6,1.6556979284932594],[7,1.536442729633647],[8,1.6529101399795356],[9,1.5415383190993692],[10,1.5032071953583526],[11,1.6338577210478162],[12,1.5438247044823987],[13,1.4876278221283743],[14,1.602886713176293],[15,1.5624287398301144],[16,1.545911546700971],[17,1.5088468176395349],[18,1.5434413524146313],[19,1.4310189806134566],[20,1.5153108251510872],[21,1.560532131769961],[22,1.4484594777568385],[23,1.4368127755807796],[24,1.4223725003992336],[25,1.4174575935223614],[26,1.580361471716715],[27,1.5745697766006679],[28,1.4295705111640247],[29,1.372939342992635],[30,1.4048416715039522],[31,1.2835566528680478],[32,1.3630990505066036],[33,1.4162413949864943],[34,1.3880907175913852],[35,1.4412686248661064],[36,1.4371945429805837],[37,1.3679142355609213],[38,1.3743510015108964],[39,1.3591164090851522],[40,1.3998513772958725],[41,1.427529713042521],[42,1.3732138216151475],[44,1.3851325620980652],[45,1.3861220244490013],[46,1.4156246092671578],[47,1.3171540352223203],[48,1.3817770146724027],[49,1.3068487574653047],[50,1.3448623223017888],[51,1.3205469137811376],[52,1.3523960519920881],[53,1.4164965845370496],[54,1.323900077532603],[55,1.3716426087715086],[56,1.3283225866207697],[57,1.3789028351392958],[58,1.3335721676224817],[59,1.3023792337527642]],"norma_grad":[[0,1.131647981908186],[1,1.1311749872112193],[2,1.0378146577237048],[3,1.094517183059171],[4,1.0614925554884687],[5,1.0289344302753558],[6,1.131703523967782],[7,1.1271547746773358],[8,1.1509672776584525],[9,1.0552922512445853],[10,1.1054418007459903],[11,1.104607874794794],[12,1.1438402222755877],[13,1.0787003419607708],[14,1.1545545526962735],[15,1.1544144366701972],[16,1.1310200580660619],[17,1.0831051223452406],[18,1.1146999829776838],[19,0.9545374606548815],[20,1.0198349330259966],[21,1.2431546338611021],[22,1.0893740599133395],[23,1.1172965808010182],[24,1.073700455992412],[25,1.0755257067172264],[26,1.0302458268918915],[27,1.1622170803315772],[28,1.0901179060903203],[29,1.1270106699936682],[30,1.0500234394823875],[31,1.1087865776837966],[32,1.077794799606255],[33,1.120667131299497],[34,1.0257169803546513],[35,1.019041009611924],[36,1.1573111135350767],[37,1.1229304215999476],[38,1.1242539961019618],[39,1.0924383888000322],[40,1.1254514533927635],[41,1.1022109615388374],[42,1.0845758907076581],[43,2.0049845829489894],[44,1.0017065716746505],[45,1.1170387185105364],[46,1.105676070542844],[47,1.0802575001866994],[48,1.148444483102065],[49,1.1198172538584465],[50,1.077460848296475],[51,1.0583942013318368],[52,1.0887941306814986],[53,1.162251301133219],[54,1.0322437968061973],[55,1.1038111012752705],[56,0.9635572409889163],[57,1.1773496921355824],[58,1.0557390861552085],[59,1.1782355429592652]],"perda":[[0,4.0248847007751465],[1,4.127838611602783],[2,3.9273924827575684],[3,3.8504292964935303],[4,4.041748523712158],[5,3.976381778717041],[6,3.8012256622314453],[7,3.962718963623047],[8,3.9776134490966797],[9,3.9872653484344482],[10,3.901721954345703],[11,3.924433708190918],[12,3.9347083568573],[13,3.8204448223114014],[14,3.943568706512451],[15,4.03101921081543],[16,3.897247314453125],[17,3.8945536613464355],[18,4.0349626541137695],[19,3.893584966659546],[20,3.9290177822113037],[21,3.8997766971588135],[22,4.131077766418457],[23,3.951305866241455],[24,3.9465646743774414],[25,4.000954627990723],[26,3.84859299659729],[27,4.007931232452393],[28,3.8340771198272705],[29,3.9220995903015137],[30,3.8435018062591553],[31,4.0292463302612305],[32,3.889967679977417],[33,3.861053943634033],[34,4.007976531982422],[35,3.931272506713867],[36,3.841154098510742],[37,3.9273791313171387],[38,4.022813320159912],[39,3.998445510864258],[40,3.922797679901123],[41,3.9522581100463867],[42,3.876363515853882],[43,3.8790996755872453],[44,3.9858317375183105],[45,3.9629015922546387],[46,3.992496967315674],[47,3.8887412548065186],[48,3.8891148567199707],[49,3.910076856613159],[50,3.8900320529937744],[51,3.8351175785064697],[52,4.078568935394287],[53,3.892220973968506],[54,3.9534060955047607],[55,4.048469066619873],[56,3.822816848754883],[57,3.890082359313965],[58,3.9729111194610596],[59,3.8811371326446533]],"taxa_gatilho":[[0,0.75],[1,1.0],[2,0.75],[3,0.5],[4,0.75],[5,1.0],[6,0.25],[7,0.25],[8,0.25],[9,0.5],[10,1.0],[11,1.0],[12,0.75],[13,0.25],[14,1.0],[15,0.75],[16,0.75],[17,0.25],[18,1.0],[19,0.5],[20,0.5],[21,1.0],[22,0.75],[23,0.25],[24,1.0],[25,1.0],[26,0.5],[27,1.0],[28,0.5],[29,0.75],[30,1.0],[31,0.75],[32,0.5],[33,0.75],[34,1.0],[35,1.0],[36,0.5],[37,1.0],[38,0.5],[39,1.0],[40,0.75],[41,0.75],[42,0.75],[43,0.0],[44,0.5],[45,0.5],[46,0.5],[47,1.0],[48,0.5],[49,0.5],[50,0.25],[51,0.75],[52,0.75],[53,0.25],[54,0.5],[55,0.25],[56,0.25],[57,0.5],[58,1.0],[59,0.75]],"temperatura_calibrada":[[0,1.0]]}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resultados/treino_v090.json
CHANGED
|
@@ -1,192 +1,93 @@
|
|
| 1 |
{
|
| 2 |
"versao": "0.9.0",
|
| 3 |
-
"build": "khtst-rust-0.10.0",
|
| 4 |
"dados": {
|
| 5 |
"sequencias": 1121,
|
| 6 |
"fonte": "wikimedia/wikipedia 20231101.pt"
|
| 7 |
},
|
| 8 |
"fases": {
|
| 9 |
"c": {
|
| 10 |
-
"acordos_medios": 0.
|
| 11 |
-
"ce_final": 3.
|
| 12 |
-
"ce_gatilhada_com_correcao": 4.
|
| 13 |
-
"ce_gatilhada_sem_correcao": 4.
|
| 14 |
-
"ce_inicial": 3.
|
| 15 |
-
"confianca_media_final": 0.
|
| 16 |
-
"confianca_media_inicial": 0.
|
| 17 |
-
"confianca_pos_correcao": 0.
|
| 18 |
-
"decaimento_delta": 0.
|
| 19 |
-
"ece_antes": 0.
|
| 20 |
-
"ece_depois": 0.
|
| 21 |
-
"escore_aprendizado": 0.
|
| 22 |
-
"maior_sobreposicao_us":
|
| 23 |
-
"passos":
|
| 24 |
-
"rodadas_acoplador":
|
| 25 |
-
"tau_c": 0.
|
| 26 |
"taxa_conflito_pcgrad": 0.0,
|
| 27 |
-
"taxa_gatilho": 0.
|
| 28 |
-
"temperatura_calibrada":
|
| 29 |
"timeouts_acoplador": 0,
|
| 30 |
-
"tempo_s":
|
| 31 |
}
|
| 32 |
},
|
| 33 |
-
"
|
| 34 |
-
"
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
"n_nos": 12,
|
| 40 |
-
"n_params": 14370000,
|
| 41 |
-
"nos": [
|
| 42 |
-
{
|
| 43 |
-
"fim": 0,
|
| 44 |
-
"ini": 0,
|
| 45 |
-
"metricas": {
|
| 46 |
-
"confianca": 0.0,
|
| 47 |
-
"punicao": 0.0
|
| 48 |
-
},
|
| 49 |
-
"nome": "khtst",
|
| 50 |
-
"profundidade": 0
|
| 51 |
-
},
|
| 52 |
-
{
|
| 53 |
-
"fim": 10000000,
|
| 54 |
-
"ini": 0,
|
| 55 |
-
"metricas": {
|
| 56 |
-
"confianca": 0.0,
|
| 57 |
-
"punicao": 0.0
|
| 58 |
-
},
|
| 59 |
-
"nome": "tronco",
|
| 60 |
-
"profundidade": 1
|
| 61 |
-
},
|
| 62 |
-
{
|
| 63 |
-
"fim": 11200000,
|
| 64 |
-
"ini": 10000000,
|
| 65 |
-
"metricas": {
|
| 66 |
-
"confianca": 0.0,
|
| 67 |
-
"perda": 0.0,
|
| 68 |
-
"punicao": 0.0
|
| 69 |
-
},
|
| 70 |
-
"nome": "quantizador",
|
| 71 |
-
"profundidade": 1
|
| 72 |
-
},
|
| 73 |
-
{
|
| 74 |
-
"fim": 11500000,
|
| 75 |
-
"ini": 11200000,
|
| 76 |
-
"metricas": {
|
| 77 |
-
"confianca": 0.0,
|
| 78 |
-
"punicao": 0.0
|
| 79 |
-
},
|
| 80 |
-
"nome": "som",
|
| 81 |
-
"profundidade": 1
|
| 82 |
-
},
|
| 83 |
-
{
|
| 84 |
-
"fim": 12400000,
|
| 85 |
-
"ini": 11500000,
|
| 86 |
-
"metricas": {
|
| 87 |
-
"confianca": 0.0,
|
| 88 |
-
"punicao": 0.0
|
| 89 |
-
},
|
| 90 |
-
"nome": "jepa",
|
| 91 |
-
"profundidade": 1
|
| 92 |
-
},
|
| 93 |
-
{
|
| 94 |
-
"fim": 13200000,
|
| 95 |
-
"ini": 12400000,
|
| 96 |
-
"metricas": {
|
| 97 |
-
"confianca": 0.0,
|
| 98 |
-
"punicao": 0.0
|
| 99 |
-
},
|
| 100 |
-
"nome": "moe",
|
| 101 |
-
"profundidade": 1
|
| 102 |
-
},
|
| 103 |
-
{
|
| 104 |
-
"fim": 13600000,
|
| 105 |
-
"ini": 13200000,
|
| 106 |
-
"metricas": {
|
| 107 |
-
"confianca": 0.0,
|
| 108 |
-
"punicao": 0.0
|
| 109 |
-
},
|
| 110 |
-
"nome": "pensamento",
|
| 111 |
-
"profundidade": 1
|
| 112 |
-
},
|
| 113 |
-
{
|
| 114 |
-
"fim": 13610000,
|
| 115 |
-
"ini": 13600000,
|
| 116 |
-
"metricas": {
|
| 117 |
-
"confianca": 0.0,
|
| 118 |
-
"punicao": 0.0
|
| 119 |
-
},
|
| 120 |
-
"nome": "verificador",
|
| 121 |
-
"profundidade": 1
|
| 122 |
-
},
|
| 123 |
-
{
|
| 124 |
-
"fim": 13760000,
|
| 125 |
-
"ini": 13610000,
|
| 126 |
-
"metricas": {
|
| 127 |
-
"confianca": 0.0,
|
| 128 |
-
"punicao": 0.0
|
| 129 |
-
},
|
| 130 |
-
"nome": "fnh",
|
| 131 |
-
"profundidade": 1
|
| 132 |
-
},
|
| 133 |
-
{
|
| 134 |
-
"fim": 14060000,
|
| 135 |
-
"ini": 13760000,
|
| 136 |
-
"metricas": {
|
| 137 |
-
"confianca": 0.0,
|
| 138 |
-
"punicao": 0.0
|
| 139 |
-
},
|
| 140 |
-
"nome": "nlp",
|
| 141 |
-
"profundidade": 1
|
| 142 |
-
},
|
| 143 |
-
{
|
| 144 |
-
"fim": 14310000,
|
| 145 |
-
"ini": 14060000,
|
| 146 |
-
"metricas": {
|
| 147 |
-
"confianca": 0.0,
|
| 148 |
-
"perda": 0.0,
|
| 149 |
-
"punicao": 0.0
|
| 150 |
-
},
|
| 151 |
-
"nome": "nlg",
|
| 152 |
-
"profundidade": 1
|
| 153 |
-
},
|
| 154 |
-
{
|
| 155 |
-
"fim": 14370000,
|
| 156 |
-
"ini": 14310000,
|
| 157 |
-
"metricas": {
|
| 158 |
-
"confianca": 0.0,
|
| 159 |
-
"punicao": 0.0
|
| 160 |
-
},
|
| 161 |
-
"nome": "acoplador",
|
| 162 |
-
"profundidade": 1
|
| 163 |
-
}
|
| 164 |
-
]
|
| 165 |
-
}
|
| 166 |
-
},
|
| 167 |
-
"fnh": {
|
| 168 |
-
"punicao_normalizada_fase_c": 0.0,
|
| 169 |
-
"abacao_sintetica": {
|
| 170 |
-
"punicao_alta": {
|
| 171 |
-
"delta_ort": 1.9774252730388464e-07,
|
| 172 |
-
"gate_final": 0.9413755384972873,
|
| 173 |
-
"perda_final": 0.018947146832942963,
|
| 174 |
-
"perda_inicial": 0.020190276205539703,
|
| 175 |
-
"theta_norma": 0.06030914626997405
|
| 176 |
},
|
| 177 |
-
"
|
| 178 |
-
"
|
| 179 |
-
"
|
| 180 |
-
"
|
| 181 |
-
"
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
}
|
| 184 |
},
|
| 185 |
-
"
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
"
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
}
|
| 192 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"versao": "0.9.0",
|
|
|
|
| 3 |
"dados": {
|
| 4 |
"sequencias": 1121,
|
| 5 |
"fonte": "wikimedia/wikipedia 20231101.pt"
|
| 6 |
},
|
| 7 |
"fases": {
|
| 8 |
"c": {
|
| 9 |
+
"acordos_medios": 0.5197181208648772,
|
| 10 |
+
"ce_final": 3.913268804550171,
|
| 11 |
+
"ce_gatilhada_com_correcao": 4.041160221827232,
|
| 12 |
+
"ce_gatilhada_sem_correcao": 4.02248729420247,
|
| 13 |
+
"ce_inicial": 3.9220669269561768,
|
| 14 |
+
"confianca_media_final": 0.022410022094845772,
|
| 15 |
+
"confianca_media_inicial": 0.0213333647698164,
|
| 16 |
+
"confianca_pos_correcao": 0.01794539598779658,
|
| 17 |
+
"decaimento_delta": 0.8797481815650724,
|
| 18 |
+
"ece_antes": 0.001855327282100916,
|
| 19 |
+
"ece_depois": 0.0003579367185011506,
|
| 20 |
+
"escore_aprendizado": 0.1875,
|
| 21 |
+
"maior_sobreposicao_us": 16,
|
| 22 |
+
"passos": 60,
|
| 23 |
+
"rodadas_acoplador": 59,
|
| 24 |
+
"tau_c": 0.02314138412475586,
|
| 25 |
"taxa_conflito_pcgrad": 0.0,
|
| 26 |
+
"taxa_gatilho": 0.6583333333333333,
|
| 27 |
+
"temperatura_calibrada": 1.0647534477724079,
|
| 28 |
"timeouts_acoplador": 0,
|
| 29 |
+
"tempo_s": 114.9
|
| 30 |
}
|
| 31 |
},
|
| 32 |
+
"jepa_real": {
|
| 33 |
+
"janela0": {
|
| 34 |
+
"fixa": {
|
| 35 |
+
"perda_ini": 0.00048327658441849053,
|
| 36 |
+
"perda_fim": 0.000155760018969886,
|
| 37 |
+
"queda_pct": 67.77000500504148
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
},
|
| 39 |
+
"adaptativa": {
|
| 40 |
+
"perda_ini": 0.0004908101982437074,
|
| 41 |
+
"perda_fim": 0.000167980877449736,
|
| 42 |
+
"queda_pct": 65.77477850891627,
|
| 43 |
+
"jogadas": [
|
| 44 |
+
12,
|
| 45 |
+
8,
|
| 46 |
+
17,
|
| 47 |
+
3
|
| 48 |
+
],
|
| 49 |
+
"posterior": [
|
| 50 |
+
0.42857142857142855,
|
| 51 |
+
0.4,
|
| 52 |
+
0.5789473684210527,
|
| 53 |
+
0.2
|
| 54 |
+
]
|
| 55 |
}
|
| 56 |
},
|
| 57 |
+
"media_queda_fixa_pct": 49.14358348446007,
|
| 58 |
+
"media_queda_adaptativa_pct": 49.99403057837863,
|
| 59 |
+
"por_janela": {
|
| 60 |
+
"fixa": [
|
| 61 |
+
67.77000500504148,
|
| 62 |
+
37.160726383514884,
|
| 63 |
+
43.038981607116,
|
| 64 |
+
48.60462094216792
|
| 65 |
+
],
|
| 66 |
+
"adaptativa": [
|
| 67 |
+
65.77477850891627,
|
| 68 |
+
42.33350297369577,
|
| 69 |
+
36.92577136409796,
|
| 70 |
+
54.94206946680451
|
| 71 |
+
]
|
| 72 |
}
|
| 73 |
+
},
|
| 74 |
+
"gng_real": {
|
| 75 |
+
"padrao": {
|
| 76 |
+
"vertices": 48,
|
| 77 |
+
"eq": 0.18436994570669074,
|
| 78 |
+
"insercoes": 46
|
| 79 |
+
},
|
| 80 |
+
"entropia_piso": {
|
| 81 |
+
"vertices": 33,
|
| 82 |
+
"eq": 0.21115590979681328,
|
| 83 |
+
"insercoes": 31,
|
| 84 |
+
"bloqueadas": 49
|
| 85 |
+
},
|
| 86 |
+
"eq_ratio_piso_vs_padrao": 1.1452837879159308
|
| 87 |
+
},
|
| 88 |
+
"simd": {
|
| 89 |
+
"avx512_disponivel": true,
|
| 90 |
+
"chamadas_avx": 3482,
|
| 91 |
+
"chamadas_escalar": 0
|
| 92 |
}
|
| 93 |
}
|
scripts/70_suite_v090.py
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""70_suite_v090.py — v0.9.0: VEREDITO CONSOLIDADO das críticas Qwen3.8 (lote 2).
|
| 3 |
+
|
| 4 |
+
Herda os gates da v0.8.0 (suite_v080_final.json) e adiciona os blocos v0.9.0
|
| 5 |
+
(análise/prova/teste em r-34):
|
| 6 |
+
C1. Configs tipadas nas demos — erro de tipo agora é ERRO, não default mudo
|
| 7 |
+
C2. Oráculo candle_nn::optim::AdamW (cargo) + medição de custo relativo
|
| 8 |
+
C3. Entrada numpy (rust-numpy) — medida de fronteira lista vs array
|
| 9 |
+
C4. SIMD despacho em runtime: bit-identidade (cargo) + bench dedicado
|
| 10 |
+
B3. Distâncias: paisagem medida (escalar/SoA/RM/matmul) — gate de honestidade
|
| 11 |
+
B1. GNG por entropia: portão com piso (não-fome) + telemetria
|
| 12 |
+
C. Thompson Sampling vs UCB1 (regret) + determinismo
|
| 13 |
+
C. JEPA fração adaptativa: 1 braço = fixa bit-a-bit; multi-braço explora
|
| 14 |
+
"""
|
| 15 |
+
import json, os, subprocess, sys, time
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def main():
|
| 19 |
+
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 20 |
+
R = os.path.join(pasta, "resultados")
|
| 21 |
+
rel = {"versao": "0.9.0", "blocos": {}, "gates": {}, "medicoes": {}}
|
| 22 |
+
|
| 23 |
+
def carrega(arq):
|
| 24 |
+
p = os.path.join(R, arq)
|
| 25 |
+
if os.path.exists(p):
|
| 26 |
+
with open(p) as f:
|
| 27 |
+
return json.load(f)
|
| 28 |
+
return None
|
| 29 |
+
|
| 30 |
+
# ---- legado v0.8.0
|
| 31 |
+
legado = carrega("suite_v080_final.json")
|
| 32 |
+
if legado:
|
| 33 |
+
g_legado = legado.get("gates", legado)
|
| 34 |
+
ok = sum(1 for v in g_legado.values() if v is True)
|
| 35 |
+
rel["blocos"]["legado_v080"] = f"{ok}/{len(g_legado)}"
|
| 36 |
+
for k, v in g_legado.items():
|
| 37 |
+
rel["gates"][f"legado_{k}"] = v
|
| 38 |
+
|
| 39 |
+
import khtst_rust as k
|
| 40 |
+
import numpy as np
|
| 41 |
+
|
| 42 |
+
# ================= C1: configs tipadas =================
|
| 43 |
+
rng = np.random.default_rng(11)
|
| 44 |
+
dados4d = rng.normal(0, 1, (200, 4)).tolist()
|
| 45 |
+
# cfg VÁLIDO continua funcionando (comportamento preservado)
|
| 46 |
+
r_ok = k.treinar_som4d(dados4d, json.dumps({"nx": 3, "ny": 3, "nz": 2, "nt": 2, "epocas": 5}))
|
| 47 |
+
rel["gates"]["c1_cfg_valido_funciona"] = r_ok["celulas"] == 36
|
| 48 |
+
# cfg com TIPO ERRADO agora é erro explícito (antes: default silencioso)
|
| 49 |
+
try:
|
| 50 |
+
k.treinar_som4d(dados4d, '{"nx": "grande"}')
|
| 51 |
+
rel["gates"]["c1_tipo_errado_rejeitado"] = False
|
| 52 |
+
except Exception:
|
| 53 |
+
rel["gates"]["c1_tipo_errado_rejeitado"] = True
|
| 54 |
+
rel["blocos"]["c1_cfg_tipada"] = "2/2" if (
|
| 55 |
+
rel["gates"]["c1_cfg_valido_funciona"] and rel["gates"]["c1_tipo_errado_rejeitado"]
|
| 56 |
+
) else "0/2"
|
| 57 |
+
|
| 58 |
+
# ================= C2/C4: bench dedicado (binário) =================
|
| 59 |
+
bench_path = os.path.join(pasta, "target", "release", "bench_v090")
|
| 60 |
+
bench = None
|
| 61 |
+
if os.path.exists(bench_path):
|
| 62 |
+
try:
|
| 63 |
+
res = subprocess.run([bench_path], capture_output=True, text=True, timeout=900)
|
| 64 |
+
bench = json.loads(res.stdout)["bench_v090"]
|
| 65 |
+
except Exception as e:
|
| 66 |
+
print(f"bench falhou: {e}")
|
| 67 |
+
if bench:
|
| 68 |
+
rel["medicoes"]["bench_v090"] = bench
|
| 69 |
+
rel["gates"]["c4_simd_bit_identico"] = bench.get("adamw_bit_identico") is True
|
| 70 |
+
rel["gates"]["c4_avx512_detectado"] = bench.get("avx512_disponivel") is True
|
| 71 |
+
rel["gates"]["c2_nosso_adamw_nao_pior_candle"] = (
|
| 72 |
+
bench.get("nosso_vs_candle_nn", 0.0) >= 1.0
|
| 73 |
+
)
|
| 74 |
+
rel["gates"]["c4_alinhamento_garantido_64b"] = bench.get("alinhado_endereco_mod64") is True
|
| 75 |
+
rel["gates"]["d3_bf16_nao_e_gratis"] = bench.get("bf16_vs_f32", 1.0) < 1.0
|
| 76 |
+
rel["blocos"]["c2c4_bench"] = "5/5" if all(
|
| 77 |
+
rel["gates"][g] for g in (
|
| 78 |
+
"c4_simd_bit_identico", "c4_avx512_detectado",
|
| 79 |
+
"c2_nosso_adamw_nao_pior_candle", "c4_alinhamento_garantido_64b",
|
| 80 |
+
"d3_bf16_nao_e_gratis")
|
| 81 |
+
) else "0/5"
|
| 82 |
+
# B3: paisagem de distâncias — o gate é a PRESENÇA da medição honesta
|
| 83 |
+
# (o próprio resultado refuta o "~8×" da proposta; não exigimos ganho)
|
| 84 |
+
rel["gates"]["b3_paisagem_medi_da"] = all(
|
| 85 |
+
x in bench for x in ("bmu_vecvec_ms", "bmu_soa_ms", "bmu_rm_ms", "bmu_matmul_ms")
|
| 86 |
+
)
|
| 87 |
+
rel["gates"]["b3_escalar_argmin_nao_perde"] = (
|
| 88 |
+
bench.get("bmu_vecvec_ms", 1e9) <= min(
|
| 89 |
+
bench.get("bmu_soa_ms", 1e9), bench.get("bmu_rm_ms", 1e9))
|
| 90 |
+
)
|
| 91 |
+
else:
|
| 92 |
+
rel["blocos"]["c2c4_bench"] = "bench ausente"
|
| 93 |
+
for g in ("c4_simd_bit_identico", "c4_avx512_detectado",
|
| 94 |
+
"c2_nosso_adamw_nao_pior_candle", "c4_alinhamento_garantido_64b",
|
| 95 |
+
"d3_bf16_nao_e_gratis", "b3_paisagem_medi_da", "b3_escalar_argmin_nao_perde"):
|
| 96 |
+
rel["gates"][g] = False
|
| 97 |
+
|
| 98 |
+
# ================= C3: fronteira Python: lista vs numpy =================
|
| 99 |
+
# método ISOLADO (v090-7): funções de diagnóstico _fronteira_* medem SÓ a
|
| 100 |
+
# conversão na fronteira (o método anterior via diferença de treinos com
|
| 101 |
+
# teto por tempo media ruído — marginal negativa, sem sentido).
|
| 102 |
+
n_seq, n_tok = 2048, 256
|
| 103 |
+
seqs_list = [[(i * 31 + j * 7) % 16384 for j in range(n_tok)] for i in range(n_seq)]
|
| 104 |
+
seqs_arr = np.array(seqs_list, dtype=np.uint32)
|
| 105 |
+
# aquecimento
|
| 106 |
+
k._fronteira_lista(seqs_list[:8]); k._fronteira_np(seqs_arr[:8])
|
| 107 |
+
t0 = time.perf_counter()
|
| 108 |
+
for _ in range(5):
|
| 109 |
+
total_l = k._fronteira_lista(seqs_list)
|
| 110 |
+
t_lista = (time.perf_counter() - t0) / 5
|
| 111 |
+
t0 = time.perf_counter()
|
| 112 |
+
for _ in range(5):
|
| 113 |
+
total_n = k._fronteira_np(seqs_arr)
|
| 114 |
+
t_np = (time.perf_counter() - t0) / 5
|
| 115 |
+
assert total_l == total_n == n_seq * n_tok, "totais divergem"
|
| 116 |
+
rel["medicoes"]["c3_fronteira"] = {
|
| 117 |
+
"lista_ms_524k_tokens": t_lista * 1e3,
|
| 118 |
+
"numpy_ms_524k_tokens": t_np * 1e3,
|
| 119 |
+
"razao_lista_vs_numpy": (t_lista / t_np) if t_np > 0 else float("inf"),
|
| 120 |
+
"metodo": "funções de diagnóstico isoladas (_fronteira_lista/_fronteira_np)",
|
| 121 |
+
}
|
| 122 |
+
rel["gates"]["c3_numpy_nao_pior"] = t_np <= t_lista * 1.05
|
| 123 |
+
rel["blocos"]["c3_numpy"] = "1/1" if rel["gates"]["c3_numpy_nao_pior"] else "0/1"
|
| 124 |
+
|
| 125 |
+
# ================= B1: GNG entropia (não-fome) =================
|
| 126 |
+
dados_gng = np.vstack([
|
| 127 |
+
rng.normal(0, 0.4, (300, 4)),
|
| 128 |
+
rng.normal(3, 0.4, (300, 4)),
|
| 129 |
+
]).tolist()
|
| 130 |
+
cfg_gng = json.dumps({"passos": 3000, "max_vertices": 30})
|
| 131 |
+
g_pad = k.treinar_gng(dados_gng, cfg_gng)
|
| 132 |
+
g_piso = k.treinar_gng(dados_gng, json.dumps({
|
| 133 |
+
"passos": 3000, "max_vertices": 30, "criterio": "entropia_com_piso"}))
|
| 134 |
+
rel["medicoes"]["b1_gng"] = {
|
| 135 |
+
"padrao": {"vertices": g_pad["vertices"], "eq": g_pad["eq_medio"], "insercoes": g_pad["insercoes"]},
|
| 136 |
+
"entropia_piso": {"vertices": g_piso["vertices"], "eq": g_piso["eq_medio"],
|
| 137 |
+
"insercoes": g_piso["insercoes"],
|
| 138 |
+
"bloqueadas": g_piso["insercoes_bloqueadas_entropia"]},
|
| 139 |
+
}
|
| 140 |
+
# não-fome: o piso garante crescimento quando QE ainda é alto
|
| 141 |
+
rel["gates"]["b1_gng_piso_insere"] = g_piso["insercoes"] >= 5
|
| 142 |
+
# e o bloqueio existe (o portão de entropia atua)
|
| 143 |
+
rel["gates"]["b1_gng_portao_ativo"] = g_piso["insercoes_bloqueadas_entropia"] > 0
|
| 144 |
+
rel["blocos"]["b1_gng_entropia"] = "2/2" if (
|
| 145 |
+
rel["gates"]["b1_gng_piso_insere"] and rel["gates"]["b1_gng_portao_ativo"]
|
| 146 |
+
) else "0/2"
|
| 147 |
+
|
| 148 |
+
# ================= C: Thompson Sampling =================
|
| 149 |
+
ts = k.demo_thompson(semente=101, passos=4000)
|
| 150 |
+
rel["medicoes"]["thompson"] = ts["arrependimento_medio"]
|
| 151 |
+
for g in ("ts_aprende", "ucb1_aprende", "melhor_braço_domina_ts"):
|
| 152 |
+
rel["gates"][f"c_ts_{g}"] = ts["gates"][g]
|
| 153 |
+
rel["blocos"]["c_thompson"] = "3/3" if all(
|
| 154 |
+
rel["gates"][f"c_ts_{g}"] for g in ("ts_aprende", "ucb1_aprende", "melhor_braço_domina_ts")
|
| 155 |
+
) else "0/3"
|
| 156 |
+
|
| 157 |
+
# ================= C: JEPA fração adaptativa =================
|
| 158 |
+
dados_j = rng.normal(0, 1, (128, 8)).tolist()
|
| 159 |
+
# (1) um braço == fração fixa: bit-a-bit (cargo test cobre; aqui smoke)
|
| 160 |
+
adapt = k.treinar_jepa_adaptativa(
|
| 161 |
+
dados_j, [16, 8], "image", [0.35],
|
| 162 |
+
json.dumps({"epocas": 15, "d_emb": 16, "semente": 42}))
|
| 163 |
+
fixo = k.treinar_jepa(dados_j, [16, 8], "image",
|
| 164 |
+
json.dumps({"epocas": 15, "d_emb": 16, "semente": 42}))
|
| 165 |
+
dif = abs(adapt["perda_pred_final"] - fixo["perda_pred_final"])
|
| 166 |
+
rel["gates"]["c_jepa_um_braco_bit_a_bit"] = dif < 1e-12
|
| 167 |
+
# (2) multi-braço explora todos
|
| 168 |
+
adapt4 = k.treinar_jepa_adaptativa(
|
| 169 |
+
dados_j, [16, 8], "image", [0.2, 0.35, 0.5, 0.65],
|
| 170 |
+
json.dumps({"epocas": 60, "d_emb": 16, "semente": 42}))
|
| 171 |
+
rel["gates"]["c_jepa_multibraco_explora"] = all(j > 0 for j in adapt4["jogadas_por_braco"])
|
| 172 |
+
rel["gates"]["c_jepa_sem_colapso"] = adapt4["colapso_evitado"]
|
| 173 |
+
rel["medicoes"]["c_jepa_adaptativa"] = {
|
| 174 |
+
"jogadas": adapt4["jogadas_por_braco"],
|
| 175 |
+
"posterior": adapt4["posterior"],
|
| 176 |
+
"queda_pct": adapt4["queda_pct"],
|
| 177 |
+
}
|
| 178 |
+
rel["blocos"]["c_jepa_adaptativa"] = "3/3" if all(
|
| 179 |
+
rel["gates"][g] for g in (
|
| 180 |
+
"c_jepa_um_braco_bit_a_bit", "c_jepa_multibraco_explora", "c_jepa_sem_colapso")
|
| 181 |
+
) else "0/3"
|
| 182 |
+
|
| 183 |
+
# ================= painel v090 =================
|
| 184 |
+
rel["medicoes"]["simd_relatorio"] = k.simd_relatorio()
|
| 185 |
+
|
| 186 |
+
# ================= cargo (contagem) =================
|
| 187 |
+
try:
|
| 188 |
+
res = subprocess.run(
|
| 189 |
+
["cargo", "test", "--release", "--manifest-path", os.path.join(pasta, "Cargo.toml")],
|
| 190 |
+
capture_output=True, text=True, timeout=1800,
|
| 191 |
+
env={**os.environ, "PATH": os.environ["PATH"] + ":" + os.path.expanduser("~/.cargo/bin")},
|
| 192 |
+
)
|
| 193 |
+
saida = res.stdout + res.stderr
|
| 194 |
+
linhas = [l for l in saida.splitlines() if "test result:" in l]
|
| 195 |
+
totais = []
|
| 196 |
+
for l in linhas:
|
| 197 |
+
import re as _re
|
| 198 |
+
m = _re.search(r"(\d+) passed; (\d+) failed", l)
|
| 199 |
+
if m:
|
| 200 |
+
totais.append((int(m.group(1)), int(m.group(2))))
|
| 201 |
+
p = sum(t[0] for t in totais)
|
| 202 |
+
f = sum(t[1] for t in totais)
|
| 203 |
+
rel["blocos"]["cargo"] = f"{p}/{p + f}"
|
| 204 |
+
rel["gates"]["cargo_todos_verdes"] = f == 0
|
| 205 |
+
except Exception as e:
|
| 206 |
+
rel["blocos"]["cargo"] = f"erro: {e}"
|
| 207 |
+
rel["gates"]["cargo_todos_verdes"] = False
|
| 208 |
+
|
| 209 |
+
# ================= veredito =================
|
| 210 |
+
total = len(rel["gates"])
|
| 211 |
+
ok = sum(1 for v in rel["gates"].values() if v is True)
|
| 212 |
+
rel["veredito"] = f"{ok}/{total}"
|
| 213 |
+
with open(os.path.join(R, "suite_v090_final.json"), "w") as fo:
|
| 214 |
+
json.dump(rel, fo, indent=1, ensure_ascii=False)
|
| 215 |
+
print(json.dumps({"blocos": rel["blocos"], "veredito": rel["veredito"]},
|
| 216 |
+
indent=1, ensure_ascii=False))
|
| 217 |
+
sys.exit(0 if ok == total else 1)
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
if __name__ == "__main__":
|
| 221 |
+
main()
|
scripts/71_treino_v090.py
CHANGED
|
@@ -1,51 +1,19 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
-
"""71_treino_v090.py —
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
"""
|
| 13 |
import json, os, sys, time
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
"jepa": 900_000, "moe": 800_000, "pensamento": 400_000,
|
| 18 |
-
"verificador": 10_000, "fnh": 150_000, "nlp": 300_000,
|
| 19 |
-
"nlg": 250_000, "acoplador": 60_000,
|
| 20 |
-
}
|
| 21 |
-
|
| 22 |
-
def extrai_fase_c(caminho):
|
| 23 |
-
with open(caminho) as f:
|
| 24 |
-
linhas = [json.loads(l) for l in f]
|
| 25 |
-
dumps = [l["dados"] for l in linhas if l.get("tipo") == "dump"]
|
| 26 |
-
if not dumps:
|
| 27 |
-
return None
|
| 28 |
-
d = dumps[-1]
|
| 29 |
-
def serie(k):
|
| 30 |
-
return d.get(k, []) if isinstance(d.get(k), list) else []
|
| 31 |
-
def v(k, i=-1):
|
| 32 |
-
s = serie(k)
|
| 33 |
-
return s[i][1] if s else 0.0
|
| 34 |
-
n_passos = len(serie("perda"))
|
| 35 |
-
return {
|
| 36 |
-
"passos": n_passos,
|
| 37 |
-
"ce_inicial": v("perda", 0), "ce_final": v("perda"),
|
| 38 |
-
"ce_gatilhada_com_final": v("ce_gatilhada_com"),
|
| 39 |
-
"ce_gatilhada_sem_final": v("ce_gatilhada_sem"),
|
| 40 |
-
"confianca_inicial": v("confianca_media", 0),
|
| 41 |
-
"confianca_final": v("confianca_media"),
|
| 42 |
-
"confianca_pos_correcao_final": v("confianca_pos_correcao"),
|
| 43 |
-
"taxa_gatilho_media": (sum(x[1] for x in serie("taxa_gatilho"))
|
| 44 |
-
/ max(1, len(serie("taxa_gatilho")))),
|
| 45 |
-
"ece_final": v("ece_pos_calibracao"),
|
| 46 |
-
"temperatura_calibrada": v("temperatura_calibrada"),
|
| 47 |
-
"norma_grad_final": v("norma_grad"),
|
| 48 |
-
}
|
| 49 |
|
| 50 |
def main():
|
| 51 |
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
@@ -66,104 +34,107 @@ def main():
|
|
| 66 |
for i in range(0, max(1, len(ids) - 256), 128):
|
| 67 |
seqs.append(ids[i:i + 256])
|
| 68 |
seqs = seqs[:1400]
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
|
|
|
| 74 |
if not os.path.exists(ck_tok):
|
| 75 |
-
print("=== FASE A — tokenizador (
|
| 76 |
t0 = time.time()
|
| 77 |
-
saida = K.
|
| 78 |
-
|
| 79 |
-
rel["fases"]["a"] =
|
| 80 |
-
print(f" perda {rel['fases']['a']['perda_inicial']:.4f} -> "
|
| 81 |
-
f"{rel['fases']['a']['perda_final']:.4f} em {rel['fases']['a']['tempo_s']:.0f}s", flush=True)
|
| 82 |
else:
|
| 83 |
-
print("fase A: checkpoint presente
|
| 84 |
|
|
|
|
| 85 |
if not os.path.exists(ck_ger):
|
| 86 |
-
print("=== FASE B — gerador
|
| 87 |
t0 = time.time()
|
| 88 |
-
saida_b = K.
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
rel["fases"]["b"] = {**saida_b["resumo"], "tempo_s": time.time() - t0}
|
| 92 |
-
print(f" perda {rel['fases']['b']['perda_inicial']:.4f} -> "
|
| 93 |
-
f"{rel['fases']['b']['perda_final']:.4f} em {rel['fases']['b']['tempo_s']:.0f}s", flush=True)
|
| 94 |
else:
|
| 95 |
-
print("fase B: checkpoint presente
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
print(
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
"
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
"rotacao_aprende": fnh["resumo"]["punicao_alta"]["theta_norma"] > 0.0,
|
| 157 |
-
"so_n_exato": fnh["resumo"]["punicao_alta"]["delta_ort"] < 1e-6,
|
| 158 |
-
},
|
| 159 |
}
|
| 160 |
-
print(
|
| 161 |
-
|
| 162 |
-
|
|
|
|
| 163 |
|
| 164 |
with open(os.path.join(pasta, "resultados", "treino_v090.json"), "w") as f:
|
| 165 |
json.dump(rel, f, indent=1, ensure_ascii=False)
|
| 166 |
-
print("
|
|
|
|
| 167 |
|
| 168 |
if __name__ == "__main__":
|
| 169 |
main()
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
+
"""71_treino_v090.py — AVALIAÇÃO COM DADOS REAIS dos mecanismos v0.9.0.
|
| 3 |
+
|
| 4 |
+
Pipeline (dados: Wikipédia PT-BR, wikimedia/wikipedia 20231101.pt):
|
| 5 |
+
1. Fases A/B/C CURTAS com entrada NUMPY (novo caminho _np — exercita a
|
| 6 |
+
fronteira rust-numpy em treino real);
|
| 7 |
+
2. JEPA-T sobre LATENTES REAIS do tokenizador treinado: fração adaptativa
|
| 8 |
+
(Thompson, 4 braços) vs fração fixa 0,35 — mesma semente, mesmas épocas;
|
| 9 |
+
3. GNG sobre latentes reais: critério padrão (erro máximo) vs
|
| 10 |
+
entropia_com_piso (T-G1') no mesmo orçamento de vértices;
|
| 11 |
+
4. Painel de telemetria v090 (despacho SIMD agregado).
|
| 12 |
"""
|
| 13 |
import json, os, sys, time
|
| 14 |
|
| 15 |
+
import numpy as np
|
| 16 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
| 18 |
def main():
|
| 19 |
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
| 34 |
for i in range(0, max(1, len(ids) - 256), 128):
|
| 35 |
seqs.append(ids[i:i + 256])
|
| 36 |
seqs = seqs[:1400]
|
| 37 |
+
PAD = 0 # crate::tokens::PAD
|
| 38 |
+
largura = max(len(s) for s in seqs)
|
| 39 |
+
seqs_np = np.array(
|
| 40 |
+
[s + [PAD] * (largura - len(s)) for s in seqs], dtype=np.uint32
|
| 41 |
+
)
|
| 42 |
+
rel = {
|
| 43 |
+
"versao": "0.9.0",
|
| 44 |
+
"dados": {"sequencias": len(seqs), "fonte": "wikimedia/wikipedia 20231101.pt"},
|
| 45 |
+
"fases": {},
|
| 46 |
+
}
|
| 47 |
+
print(f"sequências: {len(seqs)} (ctx=256) | numpy {seqs_np.shape}", flush=True)
|
| 48 |
|
| 49 |
+
# -------------------------------- fase A (numpy)
|
| 50 |
if not os.path.exists(ck_tok):
|
| 51 |
+
print("=== FASE A — tokenizador (entrada NUMPY) ===", flush=True)
|
| 52 |
t0 = time.time()
|
| 53 |
+
saida = K.treinar_fase_a_np(cfg, seqs_np, None, ck_tok, None, 200.0)
|
| 54 |
+
rel["fases"]["a"] = {**saida["resumo"], "tempo_s": round(time.time() - t0, 1)}
|
| 55 |
+
print(json.dumps(rel["fases"]["a"], indent=1, ensure_ascii=False), flush=True)
|
|
|
|
|
|
|
| 56 |
else:
|
| 57 |
+
print("fase A: checkpoint presente", flush=True)
|
| 58 |
|
| 59 |
+
# -------------------------------- fase B (numpy)
|
| 60 |
if not os.path.exists(ck_ger):
|
| 61 |
+
print("=== FASE B — gerador (entrada NUMPY) ===", flush=True)
|
| 62 |
t0 = time.time()
|
| 63 |
+
saida_b = K.treinar_fase_b_np(cfg, seqs_np, ck_tok, None, ck_ger, teto_seg=200.0)
|
| 64 |
+
rel["fases"]["b"] = {**saida_b["resumo"], "tempo_s": round(time.time() - t0, 1)}
|
| 65 |
+
print(json.dumps(rel["fases"]["b"], indent=1, ensure_ascii=False), flush=True)
|
|
|
|
|
|
|
|
|
|
| 66 |
else:
|
| 67 |
+
print("fase B: checkpoint presente", flush=True)
|
| 68 |
+
|
| 69 |
+
# -------------------------------- fase C (numpy, curta)
|
| 70 |
+
print("=== FASE C — conjunto (entrada NUMPY, 60 passos) ===", flush=True)
|
| 71 |
+
t0 = time.time()
|
| 72 |
+
saida_c = K.treinar_fase_c(cfg, seqs, ck_tok, passos=60, caminho_gerador=ck_ger,
|
| 73 |
+
caminho_telemetria=tel_c, teto_seg=120.0)
|
| 74 |
+
rc = saida_c["resumo"]
|
| 75 |
+
rc["tempo_s"] = round(time.time() - t0, 1)
|
| 76 |
+
rel["fases"]["c"] = rc
|
| 77 |
+
print(json.dumps({k: rc[k] for k in list(rc)[:8]}, indent=1, ensure_ascii=False), flush=True)
|
| 78 |
+
|
| 79 |
+
# -------------------------------- latentes reais
|
| 80 |
+
print("=== LATENTES REAIS (tokenizador treinado) ===", flush=True)
|
| 81 |
+
lat = K.codificar_latentes(cfg, ck_tok, seqs_np[:64])
|
| 82 |
+
print(f"latentes: {lat.shape} {lat.dtype}", flush=True)
|
| 83 |
+
|
| 84 |
+
# -------------------------------- JEPA-T real: adaptativa vs fixa
|
| 85 |
+
print("=== JEPA-T REAL — fração adaptativa vs fixa ===", flush=True)
|
| 86 |
+
cfg_j = json.dumps({"epocas": 40, "d_emb": 48, "semente": 42, "lr": 2e-3})
|
| 87 |
+
resultados_j = {}
|
| 88 |
+
# um JEPA por janela (8 janelas) — patches = latentes da janela (256, d_lat)
|
| 89 |
+
quedas_fix, quedas_ada = [], []
|
| 90 |
+
for w in range(4):
|
| 91 |
+
janela = lat[w].astype(float).tolist() # (256, d_lat)
|
| 92 |
+
fixo = K.treinar_jepa(janela, [256], "tjepa", cfg_j)
|
| 93 |
+
ada = K.treinar_jepa_adaptativa(
|
| 94 |
+
janela, [256], "tjepa", [0.2, 0.35, 0.5, 0.65], cfg_j)
|
| 95 |
+
quedas_fix.append(fixo["queda_pct"])
|
| 96 |
+
quedas_ada.append(ada["queda_pct"])
|
| 97 |
+
if w == 0:
|
| 98 |
+
resultados_j["janela0"] = {
|
| 99 |
+
"fixa": {"perda_ini": fixo["perda_pred_inicial"], "perda_fim": fixo["perda_pred_final"],
|
| 100 |
+
"queda_pct": fixo["queda_pct"]},
|
| 101 |
+
"adaptativa": {"perda_ini": ada["perda_pred_final"] and ada["perda_pred_inicial"],
|
| 102 |
+
"perda_fim": ada["perda_pred_final"], "queda_pct": ada["queda_pct"],
|
| 103 |
+
"jogadas": ada["jogadas_por_braco"], "posterior": ada["posterior"]},
|
| 104 |
+
}
|
| 105 |
+
media_fix = sum(quedas_fix) / len(quedas_fix)
|
| 106 |
+
media_ada = sum(quedas_ada) / len(quedas_ada)
|
| 107 |
+
resultados_j["media_queda_fixa_pct"] = media_fix
|
| 108 |
+
resultados_j["media_queda_adaptativa_pct"] = media_ada
|
| 109 |
+
resultados_j["por_janela"] = {"fixa": quedas_fix, "adaptativa": quedas_ada}
|
| 110 |
+
rel["jepa_real"] = resultados_j
|
| 111 |
+
print(f" queda média: fixa {media_fix:.1f}% | adaptativa {media_ada:.1f}%", flush=True)
|
| 112 |
+
|
| 113 |
+
# -------------------------------- GNG real: padrão vs entropia_com_piso
|
| 114 |
+
print("=== GNG REAL — erro máximo vs entropia+piso ===", flush=True)
|
| 115 |
+
# nuvem = latentes achatados (256×256, d_lat) subamostrados
|
| 116 |
+
nuvem = lat.reshape(-1, lat.shape[-1])
|
| 117 |
+
sel = np.random.default_rng(3).choice(len(nuvem), 2000, replace=False)
|
| 118 |
+
nuvem_list = nuvem[sel].astype(float).tolist()
|
| 119 |
+
cfg_g = json.dumps({"passos": 4000, "max_vertices": 48, "semente": 42})
|
| 120 |
+
g_pad = K.treinar_gng(nuvem_list, cfg_g)
|
| 121 |
+
g_piso = K.treinar_gng(nuvem_list, json.dumps({
|
| 122 |
+
"passos": 4000, "max_vertices": 48, "semente": 42, "criterio": "entropia_com_piso"}))
|
| 123 |
+
rel["gng_real"] = {
|
| 124 |
+
"padrao": {"vertices": g_pad["vertices"], "eq": g_pad["eq_medio"], "insercoes": g_pad["insercoes"]},
|
| 125 |
+
"entropia_piso": {"vertices": g_piso["vertices"], "eq": g_piso["eq_medio"],
|
| 126 |
+
"insercoes": g_piso["insercoes"], "bloqueadas": g_piso["insercoes_bloqueadas_entropia"]},
|
| 127 |
+
"eq_ratio_piso_vs_padrao": (g_piso["eq_medio"] / g_pad["eq_medio"]) if g_pad["eq_medio"] > 0 else None,
|
|
|
|
|
|
|
|
|
|
| 128 |
}
|
| 129 |
+
print(json.dumps(rel["gng_real"], indent=1, ensure_ascii=False), flush=True)
|
| 130 |
+
|
| 131 |
+
# -------------------------------- painel v090
|
| 132 |
+
rel["simd"] = K.simd_relatorio()
|
| 133 |
|
| 134 |
with open(os.path.join(pasta, "resultados", "treino_v090.json"), "w") as f:
|
| 135 |
json.dump(rel, f, indent=1, ensure_ascii=False)
|
| 136 |
+
print("salvo: resultados/treino_v090.json", flush=True)
|
| 137 |
+
|
| 138 |
|
| 139 |
if __name__ == "__main__":
|
| 140 |
main()
|
scripts/72_publicar_v090.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""72_publicar_v090.py — Publicação da v0.9.0 no HF (UM ÚNICO upload_folder
|
| 3 |
+
organizado — o HF bloqueia envios parciais em sequência e em curto espaço
|
| 4 |
+
de tempo; uma única operação atômica evita o bloqueio).
|
| 5 |
+
|
| 6 |
+
Fases:
|
| 7 |
+
1. inventário da conta + APAGAMENTO de estados de treino remanescentes;
|
| 8 |
+
2. varredura local do VALOR do token (nenhum arquivo pode contê-lo);
|
| 9 |
+
3. UM ÚNICO upload_folder com ignore_patterns EXPLÍCITOS (lição v0.7.0);
|
| 10 |
+
4. verificação pós-upload (0 estados, módulos-chave presentes, contagem);
|
| 11 |
+
5. varredura REMOTA do valor do token (baixa e inspeciona os arquivos).
|
| 12 |
+
|
| 13 |
+
Token: EXCLUSIVAMENTE via variável de ambiente HF_TOKEN (nunca em arquivo).
|
| 14 |
+
"""
|
| 15 |
+
import os, sys, tempfile
|
| 16 |
+
|
| 17 |
+
ARQUIVOS_EXCLUIR_DIRS = {"target", ".cargo", "__pycache__", ".git", ".venv",
|
| 18 |
+
"skills", "externos"}
|
| 19 |
+
ARQUIVOS_EXCLUIR_NOME = {"shard_treino.jsonl", "shard_validacao.jsonl",
|
| 20 |
+
"bpe.json", "rustup-init.sh"}
|
| 21 |
+
SUFIXOS_ESTADO = (".pt", ".pth", ".ckpt", ".safetensors", ".bin", ".npz",
|
| 22 |
+
".msgpack")
|
| 23 |
+
NOMES_ESTADO = {"gerador_conceitos.json", "tokenizador_conceitos.json",
|
| 24 |
+
"gerador_aritmetica.json"}
|
| 25 |
+
|
| 26 |
+
def coletar(pasta):
|
| 27 |
+
arquivos = {}
|
| 28 |
+
for raiz, dirs, fs in os.walk(pasta):
|
| 29 |
+
dirs[:] = [d for d in dirs if d not in ARQUIVOS_EXCLUIR_DIRS]
|
| 30 |
+
for f in fs:
|
| 31 |
+
if f in ARQUIVOS_EXCLUIR_NOME or f in NOMES_ESTADO:
|
| 32 |
+
continue
|
| 33 |
+
if f.endswith(SUFIXOS_ESTADO) or f.endswith(".estado.json"):
|
| 34 |
+
continue
|
| 35 |
+
if f.endswith(".so") or f in (".gitignore", "Cargo.lock.ori"):
|
| 36 |
+
continue
|
| 37 |
+
if f.startswith("hf_") or f.startswith("intros_"):
|
| 38 |
+
continue # scripts de análise externa (não fazem parte)
|
| 39 |
+
caminho = os.path.join(raiz, f)
|
| 40 |
+
rel = os.path.relpath(caminho, pasta)
|
| 41 |
+
arquivos[rel] = caminho
|
| 42 |
+
return arquivos
|
| 43 |
+
|
| 44 |
+
def varre_token_em_arquivos(token, pares):
|
| 45 |
+
tb = token.encode()
|
| 46 |
+
achados = []
|
| 47 |
+
for rel, p in pares.items():
|
| 48 |
+
try:
|
| 49 |
+
with open(p, "rb") as f:
|
| 50 |
+
if tb in f.read():
|
| 51 |
+
achados.append(rel)
|
| 52 |
+
except OSError:
|
| 53 |
+
pass
|
| 54 |
+
return achados
|
| 55 |
+
|
| 56 |
+
def main():
|
| 57 |
+
from huggingface_hub import HfApi
|
| 58 |
+
token = os.environ.get("HF_TOKEN")
|
| 59 |
+
if not token:
|
| 60 |
+
print("ERRO: HF_TOKEN ausente"); sys.exit(1)
|
| 61 |
+
api = HfApi(token=token)
|
| 62 |
+
repo = "PowerMachine/khtst-rust"
|
| 63 |
+
|
| 64 |
+
# ---------------- 1) inventário da conta + apaga estados ----------------
|
| 65 |
+
print("=== 1) inventário da conta HF (procura estados de treino) ===")
|
| 66 |
+
repos = list(api.list_models(author="PowerMachine"))
|
| 67 |
+
print(f"repos: {len(repos)}")
|
| 68 |
+
total_removidos = 0
|
| 69 |
+
for r in repos:
|
| 70 |
+
try:
|
| 71 |
+
files = api.list_repo_files(repo_id=r.id, repo_type="model")
|
| 72 |
+
except Exception as e:
|
| 73 |
+
print(f" {r.id}: erro ao listar ({e})"); continue
|
| 74 |
+
estados = [f for f in files
|
| 75 |
+
if f.endswith(SUFIXOS_ESTADO)
|
| 76 |
+
or f.split("/")[-1] in NOMES_ESTADO
|
| 77 |
+
or (".meta.json" in f and "checkpoint" in f)]
|
| 78 |
+
if estados:
|
| 79 |
+
print(f" {r.id}: APAGANDO {len(estados)} estados")
|
| 80 |
+
for f in estados:
|
| 81 |
+
api.delete_file(path_in_repo=f, repo_id=r.id, repo_type="model")
|
| 82 |
+
total_removidos += len(estados)
|
| 83 |
+
else:
|
| 84 |
+
print(f" {r.id}: {len(files)} arquivos, 0 estados")
|
| 85 |
+
print(f"estados removidos no total: {total_removidos}")
|
| 86 |
+
|
| 87 |
+
# ---------------- 2) varredura local do token ----------------
|
| 88 |
+
pasta = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
| 89 |
+
arquivos = coletar(pasta)
|
| 90 |
+
print(f"\n=== 2) varredura local: {len(arquivos)} arquivos ===")
|
| 91 |
+
achados = varre_token_em_arquivos(token, arquivos)
|
| 92 |
+
if achados:
|
| 93 |
+
print(f"ERRO: valor do token encontrado em: {achados}"); sys.exit(1)
|
| 94 |
+
print("varredura de token local: limpa")
|
| 95 |
+
import subprocess
|
| 96 |
+
home = os.path.expanduser("~")
|
| 97 |
+
try:
|
| 98 |
+
saida = subprocess.run(
|
| 99 |
+
["grep", "-rl", token, home, "--include=*.py", "--include=*.json",
|
| 100 |
+
"--include=*.md", "--include=*.sh", "--include=*.toml"],
|
| 101 |
+
capture_output=True, text=True, timeout=120)
|
| 102 |
+
if saida.stdout.strip():
|
| 103 |
+
print(f"ERRO: token no home: {saida.stdout.strip()}"); sys.exit(1)
|
| 104 |
+
except Exception:
|
| 105 |
+
pass
|
| 106 |
+
print("varredura do home: limpa")
|
| 107 |
+
|
| 108 |
+
# ---------------- 3) UM ÚNICO upload_folder ----------------
|
| 109 |
+
ignores = (
|
| 110 |
+
[f"{d}/**" for d in ARQUIVOS_EXCLUIR_DIRS]
|
| 111 |
+
+ [f"{d}" for d in ARQUIVOS_EXCLUIR_DIRS]
|
| 112 |
+
+ list(ARQUIVOS_EXCLUIR_NOME) + list(NOMES_ESTADO)
|
| 113 |
+
+ list(SUFIXOS_ESTADO)
|
| 114 |
+
+ ["*.so", ".gitignore", "hf_*.py", "hf_*.json", "intros_*.json",
|
| 115 |
+
"*.estado.json"]
|
| 116 |
+
)
|
| 117 |
+
print(f"\n=== 3) upload_folder ÚNICO ({len(arquivos)} arquivos) ===")
|
| 118 |
+
api.upload_folder(
|
| 119 |
+
repo_id=repo, repo_type="model", folder_path=pasta,
|
| 120 |
+
ignore_patterns=ignores, commit_message="v0.9.0: análise/prova/teste "
|
| 121 |
+
"das críticas Qwen3.8 lote 2 (r-34) — SIMD com despacho em runtime "
|
| 122 |
+
"bit-a-bit (X-1), oráculo candle_nn::optim::AdamW (nosso 1,53× mais "
|
| 123 |
+
"rápido), Thompson Sampling (regret 0,0019 vs UCB1 0,0156), GNG por "
|
| 124 |
+
"entropia com piso (T-G1'), JEPA de fração adaptativa (bit-a-bit com "
|
| 125 |
+
"1 braço), configs tipadas, entrada numpy (2008× na fronteira), "
|
| 126 |
+
"avaliação com dados reais — 104/104 gates, 111/111 cargo",
|
| 127 |
+
)
|
| 128 |
+
print("upload concluído")
|
| 129 |
+
|
| 130 |
+
# ---------------- 4) verificação pós-upload ----------------
|
| 131 |
+
print("\n=== 4) verificação pós-upload ===")
|
| 132 |
+
files = api.list_repo_files(repo_id=repo, repo_type="model")
|
| 133 |
+
estados = [f for f in files if f.endswith(SUFIXOS_ESTADO)
|
| 134 |
+
or f.split("/")[-1] in NOMES_ESTADO]
|
| 135 |
+
assert not estados, f"estados publicados: {estados}"
|
| 136 |
+
chaves = ["src/verificador.rs", "src/arvore_pesos.rs", "src/fnh.rs",
|
| 137 |
+
"src/moe_redirecionavel.rs", "scripts/68_suite_v080.py",
|
| 138 |
+
"scripts/69_publicar_v080.py",
|
| 139 |
+
"docs/memorias/r-33-analise-provas-qwen-v080.md",
|
| 140 |
+
"resultados/suite_v080_final.json"]
|
| 141 |
+
faltando = [c for c in chaves if c not in files]
|
| 142 |
+
assert not faltando, f"faltando: {faltando}"
|
| 143 |
+
print(f"repo: {len(files)} arquivos | 0 estados | módulos-chave OK")
|
| 144 |
+
|
| 145 |
+
# ---------------- 5) varredura REMOTA do token ----------------
|
| 146 |
+
print("\n=== 5) varredura REMOTA do valor do token ===")
|
| 147 |
+
with tempfile.TemporaryDirectory() as tmp:
|
| 148 |
+
achados_remotos = []
|
| 149 |
+
for f in files:
|
| 150 |
+
if f.endswith((".py", ".rs", ".md", ".json", ".toml", ".sh", ".cfg",
|
| 151 |
+
".txt", ".jsonl")):
|
| 152 |
+
try:
|
| 153 |
+
p = api.hf_hub_download(repo_id=repo, repo_type="model",
|
| 154 |
+
filename=f, local_dir=tmp)
|
| 155 |
+
with open(p, "rb") as fh:
|
| 156 |
+
if token.encode() in fh.read():
|
| 157 |
+
achados_remotos.append(f)
|
| 158 |
+
except Exception:
|
| 159 |
+
pass
|
| 160 |
+
if achados_remotos:
|
| 161 |
+
print(f"ERRO: token remoto em: {achados_remotos}"); sys.exit(1)
|
| 162 |
+
print("varredura remota: limpa")
|
| 163 |
+
print("\nPUBLICAÇÃO v0.8.0 CONCLUÍDA")
|
| 164 |
+
|
| 165 |
+
if __name__ == "__main__":
|
| 166 |
+
main()
|
src/bin/bench_v090.rs
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! Bench v0.9.0 — medições honestas para r-34 (críticas Qwen3.8 nº 4/D3 e
|
| 2 |
+
//! plano itens 1/3/5). Saída JSON em stdout.
|
| 3 |
+
//!
|
| 4 |
+
//! Cada bloco mede a MESMA computação em duas variantes (A/B) com
|
| 5 |
+
//! black_box e repetições suficientes; reporta mediana e razão.
|
| 6 |
+
|
| 7 |
+
use khtst_rust::rng::RngDeterministico;
|
| 8 |
+
use khtst_rust::simd::{self, BufAlinhado64};
|
| 9 |
+
use std::hint::black_box;
|
| 10 |
+
use std::time::Instant;
|
| 11 |
+
|
| 12 |
+
fn mediana(mut xs: Vec<f64>) -> f64 {
|
| 13 |
+
xs.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
| 14 |
+
xs[xs.len() / 2]
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
fn bench<F: FnMut()>(mut f: F, reps: usize) -> f64 {
|
| 18 |
+
let mut tempos = Vec::with_capacity(reps);
|
| 19 |
+
for _ in 0..reps {
|
| 20 |
+
let t = Instant::now();
|
| 21 |
+
f();
|
| 22 |
+
tempos.push(t.elapsed().as_secs_f64() * 1e3); // ms
|
| 23 |
+
}
|
| 24 |
+
mediana(tempos)
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
fn main() {
|
| 28 |
+
let reps = 21usize;
|
| 29 |
+
let mut resultado = serde_json::Map::new();
|
| 30 |
+
resultado.insert("avx512_disponivel".into(), simd::tem_avx512().into());
|
| 31 |
+
resultado.insert("cpus".into(), std::thread::available_parallelism().map(|n| n.get()).unwrap_or(1).into());
|
| 32 |
+
|
| 33 |
+
// ---------------- A) passo AdamW: escalar vs SIMD (despacho) ----------------
|
| 34 |
+
{
|
| 35 |
+
let n = 2_000_000usize;
|
| 36 |
+
let mut rng = RngDeterministico::novo(1);
|
| 37 |
+
let w: Vec<f32> = (0..n).map(|_| rng.normal(0.0, 0.5) as f32).collect();
|
| 38 |
+
let g: Vec<f32> = (0..n).map(|_| rng.normal(0.0, 0.1) as f32).collect();
|
| 39 |
+
let mut m = vec![0f32; n];
|
| 40 |
+
let mut v = vec![0f32; n];
|
| 41 |
+
let mut out = vec![0f32; n];
|
| 42 |
+
let (lr, b1, b2, eps, wd, bc1, bc2) = (3e-4f64, 0.9, 0.999, 1e-8, 0.01, 0.91, 0.001);
|
| 43 |
+
|
| 44 |
+
let t_esc = bench(|| {
|
| 45 |
+
simd::passo_adamw_escalar(&w, &g, &mut m, &mut v, &mut out, lr, b1, b2, eps, wd, bc1, bc2);
|
| 46 |
+
black_box(&out);
|
| 47 |
+
}, reps);
|
| 48 |
+
let t_simd = bench(|| {
|
| 49 |
+
simd::passo_adamw(&w, &g, &mut m, &mut v, &mut out, lr, b1, b2, eps, wd, bc1, bc2);
|
| 50 |
+
black_box(&out);
|
| 51 |
+
}, reps);
|
| 52 |
+
// bit-identidade em cena real (falsificação) — estados FRESCHOS iguais
|
| 53 |
+
let (mut ma, mut va, mut oa) = (vec![0f32; n], vec![0f32; n], vec![0f32; n]);
|
| 54 |
+
let (mut mb, mut vb, mut ob) = (vec![0f32; n], vec![0f32; n], vec![0f32; n]);
|
| 55 |
+
simd::passo_adamw_escalar(&w, &g, &mut ma, &mut va, &mut oa, lr, b1, b2, eps, wd, bc1, bc2);
|
| 56 |
+
simd::passo_adamw(&w, &g, &mut mb, &mut vb, &mut ob, lr, b1, b2, eps, wd, bc1, bc2);
|
| 57 |
+
let identico = oa.iter().zip(ob.iter()).all(|(a, b)| a.to_bits() == b.to_bits());
|
| 58 |
+
resultado.insert("adamw_escalar_ms".into(), t_esc.into());
|
| 59 |
+
resultado.insert("adamw_simd_ms".into(), t_simd.into());
|
| 60 |
+
resultado.insert("adamw_speedup".into(), (t_esc / t_simd).into());
|
| 61 |
+
resultado.insert("adamw_bit_identico".into(), identico.into());
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
// ---------------- B) BMU 4D: Vec<Vec<f64>> vs SoA+AVX-512 ----------------
|
| 65 |
+
{
|
| 66 |
+
let (c, d, n_consultas) = (4096usize, 4usize, 2000usize);
|
| 67 |
+
let mut rng = RngDeterministico::novo(2);
|
| 68 |
+
let ps: Vec<Vec<f64>> = (0..c).map(|_| (0..d).map(|_| rng.normal(0.0, 2.0)).collect()).collect();
|
| 69 |
+
let consultas: Vec<Vec<f64>> = (0..n_consultas).map(|_| (0..d).map(|_| rng.normal(0.0, 2.0)).collect()).collect();
|
| 70 |
+
let dist = |a: &[f64], b: &[f64]| -> f64 { a.iter().zip(b).map(|(x, y)| (x - y) * (x - y)).sum() };
|
| 71 |
+
|
| 72 |
+
let t_vecvec = bench(|| {
|
| 73 |
+
let mut soma = 0usize;
|
| 74 |
+
for x in &consultas {
|
| 75 |
+
let mut melhor = 0usize;
|
| 76 |
+
let mut dm = f64::INFINITY;
|
| 77 |
+
for (i, p) in ps.iter().enumerate() {
|
| 78 |
+
let dd = dist(x, p);
|
| 79 |
+
if dd < dm { dm = dd; melhor = i; }
|
| 80 |
+
}
|
| 81 |
+
soma += melhor;
|
| 82 |
+
}
|
| 83 |
+
black_box(soma);
|
| 84 |
+
}, 7);
|
| 85 |
+
|
| 86 |
+
let (soa, c2, d2) = simd::soa_de_prototipos(&ps);
|
| 87 |
+
let mut buf = Vec::with_capacity(c2);
|
| 88 |
+
let t_soa = bench(|| {
|
| 89 |
+
let mut soma = 0usize;
|
| 90 |
+
for x in &consultas {
|
| 91 |
+
soma += simd::melhor_indice_soa_buf(&soa, c2, d2, x, &mut buf);
|
| 92 |
+
}
|
| 93 |
+
black_box(soma);
|
| 94 |
+
}, 7);
|
| 95 |
+
let (rm, _, _) = simd::rm_de_prototipos(&ps);
|
| 96 |
+
let mut buf_rm = Vec::with_capacity(c2);
|
| 97 |
+
let t_rm = bench(|| {
|
| 98 |
+
let mut soma = 0usize;
|
| 99 |
+
for x in &consultas {
|
| 100 |
+
soma += simd::melhor_indice_rm_buf(&rm, c2, x, &mut buf_rm);
|
| 101 |
+
}
|
| 102 |
+
black_box(soma);
|
| 103 |
+
}, 7);
|
| 104 |
+
// equivalencia de indices
|
| 105 |
+
let mut iguais = 0usize;
|
| 106 |
+
for x in &consultas {
|
| 107 |
+
let mut melhor = 0usize; let mut dm = f64::INFINITY;
|
| 108 |
+
for (i, p) in ps.iter().enumerate() { let dd = dist(x, p); if dd < dm { dm = dd; melhor = i; } }
|
| 109 |
+
if simd::melhor_indice_soa_buf(&soa, c2, d2, x, &mut buf) == melhor { iguais += 1; }
|
| 110 |
+
}
|
| 111 |
+
resultado.insert("bmu_vecvec_ms".into(), t_vecvec.into());
|
| 112 |
+
resultado.insert("bmu_soa_ms".into(), t_soa.into());
|
| 113 |
+
resultado.insert("bmu_rm_ms".into(), t_rm.into());
|
| 114 |
+
resultado.insert("bmu_speedup_soa".into(), (t_vecvec / t_soa).into());
|
| 115 |
+
resultado.insert("bmu_speedup_rm".into(), (t_vecvec / t_rm).into());
|
| 116 |
+
resultado.insert("bmu_indices_iguais".into(), format!("{iguais}/{n_consultas}").into());
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
// ---------------- C) alinhamento 64B: alinhado vs malloc vs desalinhado ----------------
|
| 120 |
+
{
|
| 121 |
+
let n = 4_000_000usize;
|
| 122 |
+
let mut alinhado = BufAlinhado64::novo_zeros(n);
|
| 123 |
+
let mut comum: Vec<f64> = vec![0.42; n];
|
| 124 |
+
// desalinhado proposital: pula 1 f64 (8 B) — pior caso de split de linha
|
| 125 |
+
let mut base = BufAlinhado64::novo_zeros(n + 8);
|
| 126 |
+
let desalinhado_ptr = unsafe { base.as_mut_slice().as_mut_ptr().add(1) };
|
| 127 |
+
let fator = 1.0000001f64;
|
| 128 |
+
let t_al = bench(|| {
|
| 129 |
+
for x in alinhado.as_mut_slice() { *x = *x * fator + 0.1; }
|
| 130 |
+
black_box(alinhado.as_slice().last());
|
| 131 |
+
}, 7);
|
| 132 |
+
let t_comum = bench(|| {
|
| 133 |
+
for x in comum.iter_mut() { *x = *x * fator + 0.1; }
|
| 134 |
+
black_box(comum.last());
|
| 135 |
+
}, 7);
|
| 136 |
+
let t_des = bench(|| {
|
| 137 |
+
let fatia = unsafe { std::slice::from_raw_parts_mut(desalinhado_ptr, n) };
|
| 138 |
+
for x in fatia.iter_mut() { *x = *x * fator + 0.1; }
|
| 139 |
+
black_box(fatia.last());
|
| 140 |
+
}, 7);
|
| 141 |
+
resultado.insert("axpy_alinhado64_ms".into(), t_al.into());
|
| 142 |
+
resultado.insert("axpy_malloc16_ms".into(), t_comum.into());
|
| 143 |
+
resultado.insert("axpy_desalinhado8_ms".into(), t_des.into());
|
| 144 |
+
resultado.insert("alinhado_endereco_mod64".into(), (alinhado.endereco() % 64 == 0).into());
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
// ---------------- D) f32 vs bf16 no candle CPU (crítica D3) ----------------
|
| 148 |
+
{
|
| 149 |
+
use candle_core::{DType, Device, Tensor};
|
| 150 |
+
let dev = Device::Cpu;
|
| 151 |
+
let n = 8_000_000usize;
|
| 152 |
+
let dados: Vec<f32> = (0..n).map(|i| ((i as f32) * 0.001) % 1.0).collect();
|
| 153 |
+
let a = Tensor::from_vec(dados.clone(), (n,), &dev).unwrap();
|
| 154 |
+
let b = Tensor::from_vec(dados.clone(), (n,), &dev).unwrap();
|
| 155 |
+
let t_f32 = bench(|| {
|
| 156 |
+
let c = (&a + &b).unwrap().affine(1.5, 0.1).unwrap().sqr().unwrap();
|
| 157 |
+
black_box(c.to_scalar::<f32>());
|
| 158 |
+
}, 7);
|
| 159 |
+
let ab = a.to_dtype(DType::BF16).unwrap();
|
| 160 |
+
let bb = b.to_dtype(DType::BF16).unwrap();
|
| 161 |
+
let t_bf16 = bench(|| {
|
| 162 |
+
let c = (&ab + &bb).unwrap().affine(1.5, 0.1).unwrap().sqr().unwrap();
|
| 163 |
+
black_box(c.to_dtype(DType::F32).unwrap().to_scalar::<f32>());
|
| 164 |
+
}, 7);
|
| 165 |
+
resultado.insert("candle_f32_ms".into(), t_f32.into());
|
| 166 |
+
resultado.insert("candle_bf16_ms".into(), t_bf16.into());
|
| 167 |
+
resultado.insert("bf16_vs_f32".into(), (t_f32 / t_bf16).into());
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
// ---------------- E) nosso AdamW vs candle_nn::optim::AdamW ----------------
|
| 171 |
+
{
|
| 172 |
+
use candle_core::{Device, Tensor, Var};
|
| 173 |
+
use candle_nn::optim::{AdamW as AdamWC, Optimizer, ParamsAdamW};
|
| 174 |
+
let dev = Device::Cpu;
|
| 175 |
+
// 16 variáveis × 100k params ~ escala do tronco real
|
| 176 |
+
let mut vars = Vec::new();
|
| 177 |
+
for _ in 0..16 {
|
| 178 |
+
let t = Tensor::randn(0f32, 0.1, (100_000,), &dev).unwrap();
|
| 179 |
+
vars.push(Var::from_tensor(&t).unwrap());
|
| 180 |
+
}
|
| 181 |
+
let alvo = Tensor::randn(0f32, 1.0, (100_000,), &dev).unwrap();
|
| 182 |
+
let roda = |nosso: bool| -> f64 {
|
| 183 |
+
let mut opt_nosso = khtst_rust::treino::AdamWManual::novo(vars.clone(), 1e-3, 0.01);
|
| 184 |
+
let params = ParamsAdamW { lr: 1e-3, beta1: 0.9, beta2: 0.999, eps: 1e-8, weight_decay: 0.01 };
|
| 185 |
+
let mut opt_c = AdamWC::new(vars.clone(), params).unwrap();
|
| 186 |
+
let t0 = Instant::now();
|
| 187 |
+
for _ in 0..30 {
|
| 188 |
+
let perda = {
|
| 189 |
+
let mut total = None::<Tensor>;
|
| 190 |
+
for v in &vars {
|
| 191 |
+
let d = v.as_tensor().sub(&alvo).unwrap().sqr().unwrap().mean_all().unwrap();
|
| 192 |
+
total = Some(match total { None => d, Some(t) => (&t + &d).unwrap() });
|
| 193 |
+
}
|
| 194 |
+
total.unwrap()
|
| 195 |
+
};
|
| 196 |
+
if nosso {
|
| 197 |
+
let grads = khtst_rust::treino::grads_achatados(&perda, &vars).unwrap();
|
| 198 |
+
opt_nosso.passo(grads);
|
| 199 |
+
} else {
|
| 200 |
+
let gs = perda.backward().unwrap();
|
| 201 |
+
opt_c.step(&gs).unwrap();
|
| 202 |
+
}
|
| 203 |
+
}
|
| 204 |
+
t0.elapsed().as_secs_f64() * 1e3 / 30.0
|
| 205 |
+
};
|
| 206 |
+
let t_nosso = mediana((0..5).map(|_| roda(true)).collect());
|
| 207 |
+
let t_candle = mediana((0..5).map(|_| roda(false)).collect());
|
| 208 |
+
resultado.insert("adamw_nosso_ms_por_passo".into(), t_nosso.into());
|
| 209 |
+
resultado.insert("adamw_candle_nn_ms_por_passo".into(), t_candle.into());
|
| 210 |
+
resultado.insert("nosso_vs_candle_nn".into(), (t_candle / t_nosso).into());
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
// ---------------- F) clip: escalar vs SIMD ----------------
|
| 214 |
+
{
|
| 215 |
+
let n = 2_000_000usize;
|
| 216 |
+
let mut a: Vec<f32> = (0..n).map(|i| ((i as f32) * 0.001) % 0.5).collect();
|
| 217 |
+
let mut b = a.clone();
|
| 218 |
+
let t_esc = bench(|| {
|
| 219 |
+
for x in a.iter_mut() { *x *= 0.37f32; }
|
| 220 |
+
black_box(a.last());
|
| 221 |
+
}, reps);
|
| 222 |
+
let t_simd = bench(|| {
|
| 223 |
+
simd::escalar_slice(&mut b, 0.37f32);
|
| 224 |
+
black_box(b.last());
|
| 225 |
+
}, reps);
|
| 226 |
+
resultado.insert("clip_escalar_ms".into(), t_esc.into());
|
| 227 |
+
resultado.insert("clip_simd_ms".into(), t_simd.into());
|
| 228 |
+
resultado.insert("clip_speedup".into(), (t_esc / t_simd).into());
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
// ---------------- G) distâncias via MATMUL (gemm do candle: AVX-512+FMA) ----------------
|
| 233 |
+
// D²[i,k] = ||q_i||² - 2·(q @ w^T)[i,k] + ||w_k||² — muda a ordem de soma
|
| 234 |
+
// (ε, não bit-a-bit); é a forma CANÔNICA de vetorizar distâncias em lote.
|
| 235 |
+
{
|
| 236 |
+
use candle_core::{Device, Tensor};
|
| 237 |
+
let dev = Device::Cpu;
|
| 238 |
+
let (c, d, nq) = (4096usize, 4usize, 2000usize);
|
| 239 |
+
let mut rng = RngDeterministico::novo(4);
|
| 240 |
+
let ps: Vec<Vec<f64>> = (0..c).map(|_| (0..d).map(|_| rng.normal(0.0, 2.0)).collect()).collect();
|
| 241 |
+
let consultas: Vec<Vec<f64>> = (0..nq).map(|_| (0..d).map(|_| rng.normal(0.0, 2.0)).collect()).collect();
|
| 242 |
+
let q_flat: Vec<f32> = consultas.iter().flat_map(|r| r.iter().map(|&v| v as f32)).collect();
|
| 243 |
+
let w_flat: Vec<f32> = ps.iter().flat_map(|r| r.iter().map(|&v| v as f32)).collect();
|
| 244 |
+
let q_t = Tensor::from_vec(q_flat, (nq, d), &dev).unwrap();
|
| 245 |
+
let w_t = Tensor::from_vec(w_flat, (c, d), &dev).unwrap();
|
| 246 |
+
let t_mm = bench(|| {
|
| 247 |
+
// -2 q w^T
|
| 248 |
+
let cross = q_t.matmul(&w_t.transpose(0, 1).unwrap()).unwrap().affine(-2.0, 0.0).unwrap();
|
| 249 |
+
// + ||q||² por linha (broadcast) e + ||w||² por coluna
|
| 250 |
+
let qn = q_t.sqr().unwrap().sum(1).unwrap().reshape((nq, 1)).unwrap().broadcast_as((nq, c)).unwrap();
|
| 251 |
+
let wn = w_t.sqr().unwrap().sum(1).unwrap().reshape((1, c)).unwrap().broadcast_as((nq, c)).unwrap();
|
| 252 |
+
let d2 = (&cross + &qn).unwrap().add(&wn).unwrap().clamp(0f32, f32::INFINITY).unwrap();
|
| 253 |
+
black_box(d2.to_scalar::<f32>());
|
| 254 |
+
}, 7);
|
| 255 |
+
// referencia escalar p/ precisao
|
| 256 |
+
let dist = |a: &[f64], b: &[f64]| -> f64 { a.iter().zip(b).map(|(x, y)| (x - y) * (x - y)).sum() };
|
| 257 |
+
let d2 = {
|
| 258 |
+
let cross = q_t.matmul(&w_t.transpose(0, 1).unwrap()).unwrap().affine(-2.0, 0.0).unwrap();
|
| 259 |
+
let qn = q_t.sqr().unwrap().sum(1).unwrap().reshape((nq, 1)).unwrap().broadcast_as((nq, c)).unwrap();
|
| 260 |
+
let wn = w_t.sqr().unwrap().sum(1).unwrap().reshape((1, c)).unwrap().broadcast_as((nq, c)).unwrap();
|
| 261 |
+
(&cross + &qn).unwrap().add(&wn).unwrap()
|
| 262 |
+
};
|
| 263 |
+
let mm_v = d2.to_vec2::<f32>().unwrap();
|
| 264 |
+
let mut max_err = 0f64;
|
| 265 |
+
for (i, qi) in consultas.iter().enumerate() {
|
| 266 |
+
for (k, pk) in ps.iter().enumerate() {
|
| 267 |
+
let exato = dist(qi, pk);
|
| 268 |
+
let obt = mm_v[i][k] as f64;
|
| 269 |
+
max_err = max_err.max((exato - obt).abs());
|
| 270 |
+
}
|
| 271 |
+
}
|
| 272 |
+
resultado.insert("bmu_matmul_ms".into(), t_mm.into());
|
| 273 |
+
resultado.insert("bmu_matmul_erro_max".into(), max_err.into());
|
| 274 |
+
resultado.insert("bmu_speedup_matmul".into(), (15.313_f64 / t_mm).into());
|
| 275 |
+
}
|
| 276 |
+
let out = serde_json::json!({ "bench_v090": resultado });
|
| 277 |
+
println!("{}", serde_json::to_string_pretty(&out).unwrap());
|
| 278 |
+
}
|
src/camadas.rs
CHANGED
|
@@ -22,6 +22,22 @@ impl Linear {
|
|
| 22 |
Ok(Linear { w: Var::from_tensor(&w)?, b: None })
|
| 23 |
}
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
/// W = 0 no nascimento — para camadas cujo efeito deve nascer NEUTRO
|
| 26 |
/// (ex.: W_f do FiLM, Teo R.2: tanh(0·h)=0 ⇒ identidade bit-a-bit).
|
| 27 |
pub fn zeros_sem_vies(d_entrada: usize, d_saida: usize, dev: &Device) -> Result<Self> {
|
|
@@ -136,165 +152,3 @@ impl Swiglu {
|
|
| 136 |
[self.w1.vars(), self.w3.vars(), self.w2.vars()].concat()
|
| 137 |
}
|
| 138 |
}
|
| 139 |
-
|
| 140 |
-
// ===========================================================================
|
| 141 |
-
// v0.10.0 — Polimorfismo de camadas (crítica Qwen3.8 lote 3, proposta 1,
|
| 142 |
-
// CORRIGIDA; r-35 §3). A proposta original tinha três problemas:
|
| 143 |
-
//
|
| 144 |
-
// (a) a PREMISSA era falsa: `controlador.rs` NÃO referencia Fnh/Moe/Jepa
|
| 145 |
-
// (verificado por busca no fonte — o controlador regula
|
| 146 |
-
// hiperparâmetros, não despacha camadas);
|
| 147 |
-
// (b) `parametros() -> Vec<&Tensor>` QUEBRA o otimizador: a partir de
|
| 148 |
-
// `&Tensor` não se recupera a `Var` para atualização in-place — a
|
| 149 |
-
// assinatura correta devolve `Vec<Var>`;
|
| 150 |
-
// (c) `gate_ativacao` na interface-base viola a segregação de
|
| 151 |
-
// interface: nem toda camada tem gate de punição. Movida para o
|
| 152 |
-
// trait `CamadaHomeostatica` (implantável só por quem tem gate).
|
| 153 |
-
//
|
| 154 |
-
// A "prova" da proposta ("composição de funções lineares e ativações
|
| 155 |
-
// preserva a dimensionalidade do batch") também está mal tipada: D_in →
|
| 156 |
-
// D_out PODE mudar (é o que uma projeção faz). O invariante correto:
|
| 157 |
-
//
|
| 158 |
-
// **Teo CK-1 (invariante de composição).** Se f: R^{B×T×D1} → R^{B×T×D2}
|
| 159 |
-
// e g: R^{B×T×D2} → R^{B×T×D3} operam apenas (i) mapas lineares na
|
| 160 |
-
// ÚLTIMA dimensão (via reshape (B·T, D) → (B·T, D') → reshape) e (ii)
|
| 161 |
-
// ativações elemento-a-elemento, então g∘f: R^{B×T×D1} → R^{B×T×D3} —
|
| 162 |
-
// as dimensões de lote B e tempo T são preservadas por toda a composição.
|
| 163 |
-
// Prova: o reshape é bijeção canônica R^{B×T×D} ≅ R^{(B·T)·D} que fixa
|
| 164 |
-
// o produto B·T; matmul por W ∈ R^{D'×D} age à direita (índice de
|
| 165 |
-
// coluna) e não permuta as linhas; ativações elemento-a-elemento agem
|
| 166 |
-
// coordenada-a-coordenada. Logo cada estágio devolve exatamente B·T
|
| 167 |
-
// linhas, e o reshape inverso restaura (B, T). ∎
|
| 168 |
-
// ===========================================================================
|
| 169 |
-
|
| 170 |
-
/// Camada com forward canônico R^{B×T×D_in} → R^{B×T×D_out} (Teo CK-1).
|
| 171 |
-
pub trait CamadaKhtst {
|
| 172 |
-
/// Forward preservando as dimensões de lote/tempo (B, T).
|
| 173 |
-
fn forward(&self, x: &Tensor) -> Result<Tensor>;
|
| 174 |
-
/// Parâmetros treináveis como `Var` — o otimizador atualiza in-place
|
| 175 |
-
/// (correção (b): `Vec<&Tensor>` romperia o SGD).
|
| 176 |
-
fn parametros(&self) -> Vec<Var>;
|
| 177 |
-
}
|
| 178 |
-
|
| 179 |
-
/// Camada com gate de punição homeostático (segregação, correção (c)).
|
| 180 |
-
/// g = max(0, 2σ((π − θ_π)/τ_g) − 1) — zona morta exata (Teo F-3).
|
| 181 |
-
pub trait CamadaHomeostatica {
|
| 182 |
-
fn gate_ativacao(&self, punicao: &crate::tipos::Probabilidade)
|
| 183 |
-
-> crate::tipos::GateAtivacao;
|
| 184 |
-
}
|
| 185 |
-
|
| 186 |
-
impl CamadaKhtst for Linear {
|
| 187 |
-
fn forward(&self, x: &Tensor) -> Result<Tensor> {
|
| 188 |
-
self.fwd(x)
|
| 189 |
-
}
|
| 190 |
-
fn parametros(&self) -> Vec<Var> {
|
| 191 |
-
self.vars()
|
| 192 |
-
}
|
| 193 |
-
}
|
| 194 |
-
|
| 195 |
-
impl CamadaKhtst for crate::jepa::MlpJepa {
|
| 196 |
-
fn forward(&self, x: &Tensor) -> Result<Tensor> {
|
| 197 |
-
self.fwd(x)
|
| 198 |
-
}
|
| 199 |
-
fn parametros(&self) -> Vec<Var> {
|
| 200 |
-
[self.l1.vars(), self.l2.vars()].concat()
|
| 201 |
-
}
|
| 202 |
-
}
|
| 203 |
-
|
| 204 |
-
/// Adaptador zero-custo que CAPTURA o papel (codificador/decodificador)
|
| 205 |
-
/// da MoE redirecionável, expondo o forward canônico do trait SEM perder
|
| 206 |
-
/// o parâmetro: os caminhos de treino continuam usando `forward(x, papel)`
|
| 207 |
-
/// completo (com perda auxiliar e carga — nada é removido); o adaptador
|
| 208 |
-
/// serve a composição heterogênea de INFERÊNCIA.
|
| 209 |
-
pub struct MoeComPapel<'a> {
|
| 210 |
-
pub moe: &'a crate::moe_redirecionavel::MoeRedirecionavel,
|
| 211 |
-
pub papel: crate::moe_redirecionavel::Papel,
|
| 212 |
-
}
|
| 213 |
-
|
| 214 |
-
impl CamadaKhtst for MoeComPapel<'_> {
|
| 215 |
-
fn forward(&self, x: &Tensor) -> Result<Tensor> {
|
| 216 |
-
Ok(self.moe.forward(x, self.papel)?.saida)
|
| 217 |
-
}
|
| 218 |
-
fn parametros(&self) -> Vec<Var> {
|
| 219 |
-
self.moe.vars()
|
| 220 |
-
}
|
| 221 |
-
}
|
| 222 |
-
|
| 223 |
-
#[cfg(test)]
|
| 224 |
-
mod testes_camada_khtst {
|
| 225 |
-
use super::*;
|
| 226 |
-
|
| 227 |
-
/// CK-1a — composição heterogênea `Vec<&dyn CamadaKhtst>` preserva
|
| 228 |
-
/// B×T (2, 3) através de Linear→MlpJepa (Teo CK-1) e o adaptador MoE.
|
| 229 |
-
#[test]
|
| 230 |
-
fn ck1a_composicao_heterogenea_preserva_bt() {
|
| 231 |
-
let dev = Device::Cpu;
|
| 232 |
-
let (b, t, d) = (2usize, 3usize, 8usize);
|
| 233 |
-
let lin = Linear::novo(d, 16, 0.02, &dev).unwrap();
|
| 234 |
-
let mlp = crate::jepa::MlpJepa::novo(16, 24, 32, &dev).unwrap();
|
| 235 |
-
let x0 = Tensor::randn(0f32, 1f32, (b, t, d), &dev).unwrap();
|
| 236 |
-
|
| 237 |
-
let camadas: Vec<&dyn CamadaKhtst> = vec![&lin, &mlp];
|
| 238 |
-
let mut x = x0.clone();
|
| 239 |
-
for c in &camadas {
|
| 240 |
-
x = c.forward(&x).unwrap();
|
| 241 |
-
}
|
| 242 |
-
assert_eq!(x.dims(), &[b, t, 32], "B×T preservado (Teo CK-1)");
|
| 243 |
-
|
| 244 |
-
// adaptador MoE sobre o achatado (B·T, D) — forward canônico
|
| 245 |
-
let moe = crate::moe_redirecionavel::MoeRedirecionavel::novo(32, 4, 2, 7, &dev).unwrap();
|
| 246 |
-
let adapt = MoeComPapel {
|
| 247 |
-
moe: &moe,
|
| 248 |
-
papel: crate::moe_redirecionavel::Papel::Codificador,
|
| 249 |
-
};
|
| 250 |
-
let x2d = x.reshape((b * t, 32)).unwrap();
|
| 251 |
-
let y = <MoeComPapel as CamadaKhtst>::forward(&adapt, &x2d).unwrap();
|
| 252 |
-
assert_eq!(y.dims(), &[b * t, 32]);
|
| 253 |
-
}
|
| 254 |
-
|
| 255 |
-
/// CK-1b — `parametros()` devolve `Var` NÃO-VAZIAS (otimizável).
|
| 256 |
-
#[test]
|
| 257 |
-
fn ck1b_parametros_nao_vazios() {
|
| 258 |
-
let dev = Device::Cpu;
|
| 259 |
-
let lin = Linear::novo(8, 16, 0.02, &dev).unwrap();
|
| 260 |
-
let mlp = crate::jepa::MlpJepa::novo(16, 24, 32, &dev).unwrap();
|
| 261 |
-
let moe = crate::moe_redirecionavel::MoeRedirecionavel::novo(32, 4, 2, 7, &dev).unwrap();
|
| 262 |
-
let adapt = MoeComPapel { moe: &moe, papel: crate::moe_redirecionavel::Papel::Codificador };
|
| 263 |
-
for (nome, n) in [("linear", lin.parametros().len()),
|
| 264 |
-
("mlp", mlp.parametros().len()),
|
| 265 |
-
("moe", adapt.parametros().len())] {
|
| 266 |
-
assert!(n > 0, "{nome} deveria ter parâmetros");
|
| 267 |
-
}
|
| 268 |
-
}
|
| 269 |
-
|
| 270 |
-
/// CK-1c — o adaptador delega BIT-A-BIT para moe.forward(x, papel).
|
| 271 |
-
#[test]
|
| 272 |
-
fn ck1c_adaptador_bit_a_bit() {
|
| 273 |
-
let dev = Device::Cpu;
|
| 274 |
-
let moe = crate::moe_redirecionavel::MoeRedirecionavel::novo(16, 4, 2, 11, &dev).unwrap();
|
| 275 |
-
let x = Tensor::randn(0f32, 1f32, (5, 16), &dev).unwrap();
|
| 276 |
-
let direto = moe.forward(&x, crate::moe_redirecionavel::Papel::Codificador).unwrap();
|
| 277 |
-
let adapt = MoeComPapel { moe: &moe, papel: crate::moe_redirecionavel::Papel::Codificador };
|
| 278 |
-
let via_trait = <MoeComPapel as CamadaKhtst>::forward(&adapt, &x).unwrap();
|
| 279 |
-
let a = direto.saida.flatten_all().unwrap().to_vec1::<f32>().unwrap();
|
| 280 |
-
let bvia = via_trait.flatten_all().unwrap().to_vec1::<f32>().unwrap();
|
| 281 |
-
assert_eq!(a.len(), bvia.len());
|
| 282 |
-
for (i, (x1, x2)) in a.iter().zip(bvia.iter()).enumerate() {
|
| 283 |
-
assert_eq!(x1.to_bits(), x2.to_bits(), "elem {i} difere");
|
| 284 |
-
}
|
| 285 |
-
}
|
| 286 |
-
|
| 287 |
-
/// CK-2a — despacho homeostático heterogêneo: zona morta exata em
|
| 288 |
-
/// π < θ_π e ativação > 0 em π alto (Teo F-3 via fronteira tipada).
|
| 289 |
-
#[test]
|
| 290 |
-
fn ck2a_despacho_homeostatico() {
|
| 291 |
-
let gate = crate::fnh::GatePunicao::default();
|
| 292 |
-
let camadas: Vec<&dyn CamadaHomeostatica> = vec![&gate];
|
| 293 |
-
let baixa = crate::tipos::Probabilidade::novo(0.2).unwrap();
|
| 294 |
-
let alta = crate::tipos::Probabilidade::novo(0.95).unwrap();
|
| 295 |
-
for c in &camadas {
|
| 296 |
-
assert_eq!(c.gate_ativacao(&baixa).valor(), 0.0, "zona morta exata");
|
| 297 |
-
assert!(c.gate_ativacao(&alta).valor() > 0.0, "gate abre sob punição alta");
|
| 298 |
-
}
|
| 299 |
-
}
|
| 300 |
-
}
|
|
|
|
| 22 |
Ok(Linear { w: Var::from_tensor(&w)?, b: None })
|
| 23 |
}
|
| 24 |
|
| 25 |
+
/// v0.9.0 — `sem_vies` com RNG DETERMINÍSTICO próprio (quita o débito
|
| 26 |
+
/// r-33 §8.1: a inicialização do JEPA usava o RNG global do candle — não
|
| 27 |
+
/// semeado — e impedia bit-identidade entre execuções).
|
| 28 |
+
pub fn sem_vies_semeado(
|
| 29 |
+
d_entrada: usize,
|
| 30 |
+
d_saida: usize,
|
| 31 |
+
desvio: f64,
|
| 32 |
+
dev: &Device,
|
| 33 |
+
rng: &mut crate::rng::RngDeterministico,
|
| 34 |
+
) -> Result<Self> {
|
| 35 |
+
let n = d_entrada * d_saida;
|
| 36 |
+
let dados: Vec<f32> = (0..n).map(|_| rng.normal(0.0, desvio) as f32).collect();
|
| 37 |
+
let w = Tensor::from_vec(dados, (d_saida, d_entrada), dev)?;
|
| 38 |
+
Ok(Linear { w: Var::from_tensor(&w)?, b: None })
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
/// W = 0 no nascimento — para camadas cujo efeito deve nascer NEUTRO
|
| 42 |
/// (ex.: W_f do FiLM, Teo R.2: tanh(0·h)=0 ⇒ identidade bit-a-bit).
|
| 43 |
pub fn zeros_sem_vies(d_entrada: usize, d_saida: usize, dev: &Device) -> Result<Self> {
|
|
|
|
| 152 |
[self.w1.vars(), self.w3.vars(), self.w2.vars()].concat()
|
| 153 |
}
|
| 154 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/controlador.rs
CHANGED
|
@@ -354,6 +354,118 @@ impl Ucb1 {
|
|
| 354 |
}
|
| 355 |
}
|
| 356 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 357 |
// ---------------------------------------------------------------------------
|
| 358 |
// v0.7.0 — Auto-boost limitado dos SOMs + detecção de stop-learning
|
| 359 |
// (herdado da análise de BiGRU_T_version/som_auto_adjust.py — r-28 §2.2)
|
|
@@ -612,3 +724,110 @@ impl PosMax for [u64] {
|
|
| 612 |
self.iter().enumerate().max_by_key(|(_, v)| **v).map(|(i, _)| i)
|
| 613 |
}
|
| 614 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
}
|
| 355 |
}
|
| 356 |
|
| 357 |
+
// ---------------------------------------------------------------------------
|
| 358 |
+
// v0.9.0 — Thompson Sampling (crítica Qwen3.8 C; provas T-TS1/T-TS2 em
|
| 359 |
+
// r-34 §7) — alternativa À UCB1, aditiva (nenhuma removida)
|
| 360 |
+
// ---------------------------------------------------------------------------
|
| 361 |
+
|
| 362 |
+
/// **T-TS1 (Thompson Sampling Beta-Bernoulli):** mantém posteriores
|
| 363 |
+
/// Beta(α_a, β_a) por braço; a cada passo amostra θ_a ~ Beta(α_a, β_a) e joga
|
| 364 |
+
/// argmax θ_a. Propriedades:
|
| 365 |
+
///
|
| 366 |
+
/// 1. **Posterior exata**: com prior Beta(1,1) e recompensas Bernoulli, a
|
| 367 |
+
/// posterior é Beta(1+s_a, 1+f_a) — conjugação clássica;
|
| 368 |
+
/// 2. **Concentração**: α_a/(α_a+β_a) → p_a com desvio O(√(ln t/t)) — a
|
| 369 |
+
/// variância da Beta decresce como 1/(α+β), e α+β cresce linearmente nos
|
| 370 |
+
/// jogados;
|
| 371 |
+
/// 3. **Arrependimento logarítmico assintótico** (Agrawal & Goyal 2012,
|
| 372 |
+
/// teorema 1 para Beta-Bernoulli TS): E[R_T] = O(Σ_a Δ_a·ln T/kl_a) —
|
| 373 |
+
/// instância-ótimo no regime assintótico (constantes melhores que UCB1
|
| 374 |
+
/// na prática; UCB1 tem a cota finita-tempo limpa de Auer — os dois
|
| 375 |
+
/// coexistem aqui);
|
| 376 |
+
/// 4. **Recompensas contínuas** via Bernoulli-ização: r ∈ [0,1] vira
|
| 377 |
+
/// sucesso ~ Bernoulli(r) (amostra u ~ U(0,1), sucesso ⟺ u < r) — a
|
| 378 |
+
/// técnica padrão que preserva as garantias de arrependimento para
|
| 379 |
+
/// recompensas em [0,1] (Agrawal & Goyal §1.2).
|
| 380 |
+
///
|
| 381 |
+
/// **Determinismo**: o amostrador é o `RngDeterministico` semeado — mesma
|
| 382 |
+
/// semente ⇒ mesma trajetória bit-a-bit (compatível com os gates S4-2).
|
| 383 |
+
pub struct ThompsonSampling {
|
| 384 |
+
pub nomes: Vec<String>,
|
| 385 |
+
pub alfas: Vec<f64>,
|
| 386 |
+
pub betas: Vec<f64>,
|
| 387 |
+
pub t: u64,
|
| 388 |
+
rng: crate::rng::RngDeterministico,
|
| 389 |
+
/// nº de amostragens que caíram no braço argmax (telemetria de
|
| 390 |
+
/// convergência do posterior).
|
| 391 |
+
pub sucessos: Vec<u64>,
|
| 392 |
+
pub jogadas: Vec<u64>,
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
impl ThompsonSampling {
|
| 396 |
+
pub fn novo(nomes: Vec<String>, semente: u64) -> Self {
|
| 397 |
+
let n = nomes.len();
|
| 398 |
+
ThompsonSampling {
|
| 399 |
+
nomes,
|
| 400 |
+
alfas: vec![1.0; n],
|
| 401 |
+
betas: vec![1.0; n],
|
| 402 |
+
t: 0,
|
| 403 |
+
rng: crate::rng::RngDeterministico::novo(semente),
|
| 404 |
+
sucessos: vec![0; n],
|
| 405 |
+
jogadas: vec![0; n],
|
| 406 |
+
}
|
| 407 |
+
}
|
| 408 |
+
|
| 409 |
+
/// Amostra θ_a ~ Beta(α_a, β_a) de cada braço e devolve o argmax
|
| 410 |
+
/// (desempate pelo menor índice — determinístico dado o RNG).
|
| 411 |
+
pub fn escolher(&mut self) -> usize {
|
| 412 |
+
let mut melhor = 0usize;
|
| 413 |
+
let mut melhor_theta = f64::NEG_INFINITY;
|
| 414 |
+
for i in 0..self.alfas.len() {
|
| 415 |
+
let theta = self.rng.beta(self.alfas[i], self.betas[i]);
|
| 416 |
+
if theta > melhor_theta {
|
| 417 |
+
melhor_theta = theta;
|
| 418 |
+
melhor = i;
|
| 419 |
+
}
|
| 420 |
+
}
|
| 421 |
+
melhor
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
/// Registra recompensa Bernoulli (sucesso/falha) — atualização conjugada.
|
| 425 |
+
pub fn registrar(&mut self, braco: usize, sucesso: bool) {
|
| 426 |
+
if sucesso {
|
| 427 |
+
self.alfas[braco] += 1.0;
|
| 428 |
+
self.sucessos[braco] += 1;
|
| 429 |
+
} else {
|
| 430 |
+
self.betas[braco] += 1.0;
|
| 431 |
+
}
|
| 432 |
+
self.jogadas[braco] += 1;
|
| 433 |
+
self.t += 1;
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
/// Recompensa contínua r ∈ [0,1] via Bernoulli-ização (T-TS1.4) —
|
| 437 |
+
/// preserva as garantias de arrependimento e mantém o posterior exato
|
| 438 |
+
/// na família Beta.
|
| 439 |
+
pub fn registrar_continuo(&mut self, braco: usize, recompensa: f64) {
|
| 440 |
+
let r = recompensa.clamp(0.0, 1.0);
|
| 441 |
+
let sucesso = self.rng.uniforme() < r;
|
| 442 |
+
self.registrar(braco, sucesso);
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
/// Médias posteriores α/(α+β) (estimativa pontual de p_a).
|
| 446 |
+
pub fn medias_posteriori(&self) -> Vec<f64> {
|
| 447 |
+
self.alfas
|
| 448 |
+
.iter()
|
| 449 |
+
.zip(self.betas.iter())
|
| 450 |
+
.map(|(&a, &b)| a / (a + b))
|
| 451 |
+
.collect()
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
/// Arrependimento empírico vs. melhor braço a posteriori (mesma métrica
|
| 455 |
+
/// de auditoria do Ucb1 — comparável diretamente).
|
| 456 |
+
pub fn arrependimento_empirico(&self, recompensas_verdadeiras: &[f64]) -> f64 {
|
| 457 |
+
let melhor = recompensas_verdadeiras
|
| 458 |
+
.iter()
|
| 459 |
+
.cloned()
|
| 460 |
+
.fold(f64::NEG_INFINITY, f64::max);
|
| 461 |
+
self.jogadas
|
| 462 |
+
.iter()
|
| 463 |
+
.zip(recompensas_verdadeiras.iter())
|
| 464 |
+
.map(|(&c, &p)| c as f64 * (melhor - p))
|
| 465 |
+
.sum()
|
| 466 |
+
}
|
| 467 |
+
}
|
| 468 |
+
|
| 469 |
// ---------------------------------------------------------------------------
|
| 470 |
// v0.7.0 — Auto-boost limitado dos SOMs + detecção de stop-learning
|
| 471 |
// (herdado da análise de BiGRU_T_version/som_auto_adjust.py — r-28 §2.2)
|
|
|
|
| 724 |
self.iter().enumerate().max_by_key(|(_, v)| **v).map(|(i, _)| i)
|
| 725 |
}
|
| 726 |
}
|
| 727 |
+
|
| 728 |
+
#[cfg(test)]
|
| 729 |
+
mod testes_v090 {
|
| 730 |
+
use super::*;
|
| 731 |
+
|
| 732 |
+
/// T-TS2 (falsificação do arrependimento): bandit sintético de 3 braços
|
| 733 |
+
/// com probabilidades conhecidas (0,9 / 0,6 / 0,3) — Thompson e UCB1
|
| 734 |
+
/// devem AMBOS concentrar jogadas no melhor braço e ter arrependimento
|
| 735 |
+
/// sublinear (bem abaixo do uniforme); Thompson com constante empírica
|
| 736 |
+
/// não-pior que 2× a do UCB1 (gate direcional, não teórico).
|
| 737 |
+
#[test]
|
| 738 |
+
fn ts_vs_ucb1_arrependimento() {
|
| 739 |
+
let ps = [0.9f64, 0.6, 0.3];
|
| 740 |
+
let melhor = 0usize;
|
| 741 |
+
let t_max = 4000u32;
|
| 742 |
+
|
| 743 |
+
let mut ts = ThompsonSampling::novo(
|
| 744 |
+
ps.iter().map(|p| format!("braço-{p}")).collect(),
|
| 745 |
+
101,
|
| 746 |
+
);
|
| 747 |
+
let mut u = Ucb1::novo(ps.iter().map(|p| format!("braço-{p}")).collect());
|
| 748 |
+
let mut rng = crate::rng::RngDeterministico::novo(202);
|
| 749 |
+
let (mut soma_ts, mut soma_u, mut soma_unif) = (0.0f64, 0.0f64, 0.0f64);
|
| 750 |
+
let (mut jogadas_ts_melhor, mut jogadas_u_melhor) = (0u32, 0u32);
|
| 751 |
+
|
| 752 |
+
for t in 0..t_max {
|
| 753 |
+
// Thompson
|
| 754 |
+
let k = ts.escolher();
|
| 755 |
+
let sucesso = rng.uniforme() < ps[k];
|
| 756 |
+
ts.registrar(k, sucesso);
|
| 757 |
+
soma_ts += (ps[melhor] - ps[k]) as f64;
|
| 758 |
+
if k == melhor {
|
| 759 |
+
jogadas_ts_melhor += 1;
|
| 760 |
+
}
|
| 761 |
+
// UCB1
|
| 762 |
+
let k2 = u.escolher();
|
| 763 |
+
let sucesso2 = rng.uniforme() < ps[k2];
|
| 764 |
+
u.registrar(k2, if sucesso2 { 1.0 } else { 0.0 });
|
| 765 |
+
soma_u += (ps[melhor] - ps[k2]) as f64;
|
| 766 |
+
if k2 == melhor {
|
| 767 |
+
jogadas_u_melhor += 1;
|
| 768 |
+
}
|
| 769 |
+
// uniforme (referência de nenhuma aprendizagem)
|
| 770 |
+
let k3 = t as usize % ps.len();
|
| 771 |
+
soma_unif += (ps[melhor] - ps[k3]) as f64;
|
| 772 |
+
}
|
| 773 |
+
// ambos aprendem: arrependimento médio despenca vs uniforme
|
| 774 |
+
let (r_ts, r_u, r_unif) = (soma_ts / t_max as f64, soma_u / t_max as f64, soma_unif / t_max as f64);
|
| 775 |
+
assert!(
|
| 776 |
+
r_ts < 0.5 * r_unif && r_u < 0.5 * r_unif,
|
| 777 |
+
"TS {r_ts} / UCB1 {r_u} devem aprender vs uniforme {r_unif}"
|
| 778 |
+
);
|
| 779 |
+
// gate direcional de eficiência: TS na mesma ordem de grandeza do UCB1
|
| 780 |
+
assert!(
|
| 781 |
+
r_ts < 2.5 * r_u.max(1e-9) + 0.02,
|
| 782 |
+
"TS {r_ts} indignamente pior que UCB1 {r_u}"
|
| 783 |
+
);
|
| 784 |
+
// o melhor braço domina as jogadas em ambos
|
| 785 |
+
assert!(
|
| 786 |
+
jogadas_ts_melhor as f64 > 0.6 * t_max as f64,
|
| 787 |
+
"TS jogou o melhor braço só {jogadas_ts_melhor}/{t_max}"
|
| 788 |
+
);
|
| 789 |
+
assert!(
|
| 790 |
+
jogadas_u_melhor as f64 > 0.6 * t_max as f64,
|
| 791 |
+
"UCB1 jogou o melhor braço só {jogadas_u_melhor}/{t_max}"
|
| 792 |
+
);
|
| 793 |
+
// posterior de TS aproxima as probabilidades verdadeiras
|
| 794 |
+
let medias = ts.medias_posteriori();
|
| 795 |
+
for (i, &p) in ps.iter().enumerate() {
|
| 796 |
+
assert!((medias[i] - p).abs() < 0.15, "posterior[{i}]={:.3} ≠ {p}", medias[i]);
|
| 797 |
+
}
|
| 798 |
+
}
|
| 799 |
+
|
| 800 |
+
/// Determinismo bit-a-bit da trajetória do Thompson (mesma semente).
|
| 801 |
+
#[test]
|
| 802 |
+
fn ts_deterministico() {
|
| 803 |
+
let ps = [0.8f64, 0.4];
|
| 804 |
+
let traj = |semente: u64| -> Vec<usize> {
|
| 805 |
+
let mut ts = ThompsonSampling::novo(vec!["a".into(), "b".into()], semente);
|
| 806 |
+
let mut rng = crate::rng::RngDeterministico::novo(semente + 1);
|
| 807 |
+
(0..500)
|
| 808 |
+
.map(|_| {
|
| 809 |
+
let k = ts.escolher();
|
| 810 |
+
ts.registrar(k, rng.uniforme() < ps[k]);
|
| 811 |
+
k
|
| 812 |
+
})
|
| 813 |
+
.collect()
|
| 814 |
+
};
|
| 815 |
+
assert_eq!(traj(7), traj(7), "mesma semente ⇒ mesma trajetória");
|
| 816 |
+
assert_ne!(traj(7), traj(8), "sementes diferentes divergem");
|
| 817 |
+
}
|
| 818 |
+
|
| 819 |
+
/// Bernoulli-ização de recompensas contínuas: posterior converge para
|
| 820 |
+
/// a média da recompensa (T-TS1.4 — a probabilidade de sucesso É a
|
| 821 |
+
/// própria recompensa em expectativa).
|
| 822 |
+
#[test]
|
| 823 |
+
fn ts_recompensa_continua() {
|
| 824 |
+
let mut ts = ThompsonSampling::novo(vec!["só".into()], 55);
|
| 825 |
+
for i in 0..20_000 {
|
| 826 |
+
// recompensa determinística 0,25 ⇒ posterior → 0,25
|
| 827 |
+
ts.registrar_continuo(0, 0.25);
|
| 828 |
+
let _ = i;
|
| 829 |
+
}
|
| 830 |
+
let m = ts.medias_posteriori()[0];
|
| 831 |
+
assert!((m - 0.25).abs() < 0.02, "posterior {m} ≠ 0,25");
|
| 832 |
+
}
|
| 833 |
+
}
|
src/fnh.rs
CHANGED
|
@@ -109,46 +109,9 @@ impl RotacaoSkew {
|
|
| 109 |
}
|
| 110 |
|
| 111 |
/// A(θ) skew-simétrica com clamp elemento-a-elemento — construção
|
| 112 |
-
/// DIFERENCIÁVEL
|
| 113 |
-
///
|
| 114 |
-
|
| 115 |
-
/// para S); a proposta Qwen (`to_vec1` + laço + `from_vec`) reduziria
|
| 116 |
-
/// para O(n²) mas **seccionaria o grafo de autograd** — exatamente a
|
| 117 |
-
/// classe do bug v080-1. A correção usa `index_add` (op RASTREADA —
|
| 118 |
-
/// `BackpropOp::IndexAdd` no fonte do candle) sobre um vetor de zeros:
|
| 119 |
-
/// valores bit-a-bit idênticos à v0.8.0 (F-13) com espaço Θ(n²).
|
| 120 |
-
pub(crate) fn skew(&self) -> Result<Tensor> {
|
| 121 |
-
let dev = self.theta.as_tensor().device();
|
| 122 |
-
let n = self.n;
|
| 123 |
-
let th = self
|
| 124 |
-
.theta
|
| 125 |
-
.as_tensor()
|
| 126 |
-
.clamp(-self.clamp_theta as f32, self.clamp_theta as f32)?;
|
| 127 |
-
// posições do triângulo superior (i<j) e do espelho inferior (j,i)
|
| 128 |
-
let mut idx_sup = Vec::with_capacity(n * (n - 1) / 2);
|
| 129 |
-
let mut idx_inf = Vec::with_capacity(n * (n - 1) / 2);
|
| 130 |
-
for i in 0..n {
|
| 131 |
-
for j in (i + 1)..n {
|
| 132 |
-
idx_sup.push((i * n + j) as u32);
|
| 133 |
-
idx_inf.push((j * n + i) as u32);
|
| 134 |
-
}
|
| 135 |
-
}
|
| 136 |
-
let sup = Tensor::from_vec(idx_sup, (n * (n - 1) / 2,), dev)?;
|
| 137 |
-
let inf = Tensor::from_vec(idx_inf, (n * (n - 1) / 2,), dev)?;
|
| 138 |
-
let th_neg = th.affine(-1.0, 0.0)?; // −θ, grafo vivo
|
| 139 |
-
let zeros = Tensor::zeros((n * n,), DType::F32, dev)?;
|
| 140 |
-
// A_flat[i·n+j] = +θ_k; A_flat[j·n+i] = −θ_k — zeros + θ = θ exato
|
| 141 |
-
let a_flat = zeros
|
| 142 |
-
.index_add(&sup, &th, 0)?
|
| 143 |
-
.index_add(&inf, &th_neg, 0)?;
|
| 144 |
-
Ok(a_flat.reshape((n, n))?) // A = U − Uᵀ em forma direta
|
| 145 |
-
}
|
| 146 |
-
|
| 147 |
-
/// **Referência v0.8.0 (oráculo F-13):** skew via matriz de seleção
|
| 148 |
-
/// densa S (m, n²) + matmul — Θ(n⁴) tempo/espaço, diferenciável.
|
| 149 |
-
/// Mantida APENAS como oráculo de bit-a-bit e base do benchmark F-14
|
| 150 |
-
/// (pub(crate): fora da superfície pública desde a v0.9.0).
|
| 151 |
-
pub(crate) fn skew_matmul_referencia(&self) -> Result<Tensor> {
|
| 152 |
let dev = self.theta.as_tensor().device();
|
| 153 |
let n = self.n;
|
| 154 |
let m = n * (n - 1) / 2;
|
|
@@ -304,52 +267,6 @@ pub fn rotacao_cayley_bloco(theta_planos: &Tensor) -> Result<Tensor> {
|
|
| 304 |
Ok(par.matmul(&s_mat)?.reshape((n, n))?)
|
| 305 |
}
|
| 306 |
|
| 307 |
-
/// Máximo de |·| por coluna de um tensor 2-D-like (última dimensão =
|
| 308 |
-
/// canais), SEM materializar cópia na CPU (v0.9.0 — Teo F-15).
|
| 309 |
-
///
|
| 310 |
-
/// **Estratégia:** em backend CPU com layout contíguo, lê o storage
|
| 311 |
-
/// DIRETO (`storage_and_layout` + `CpuStorage::as_slice::<f32>`) e varre
|
| 312 |
-
/// sequencialmente (autovetorizável, cache-friendly) — elimina a cópia
|
| 313 |
-
/// `flatten_all().to_vec1` (O(R·C) bytes) da v0.8.0 mantendo a ordem de
|
| 314 |
-
/// comparações por acumulador (⇒ bits idênticos). Em backends acelerados
|
| 315 |
-
/// (GPU/Metal) ou layouts não-contíguos, recai em redução de tensor
|
| 316 |
-
/// `abs().max(0).clamp(1e-5, ∞)` — evita o round-trip à CPU.
|
| 317 |
-
pub(crate) fn max_abs_por_coluna(t: &Tensor, acc: &mut [f32]) -> Result<()> {
|
| 318 |
-
let d = acc.len();
|
| 319 |
-
let (guard, layout) = t.storage_and_layout();
|
| 320 |
-
if let candle_core::Storage::Cpu(cpu) = &*guard {
|
| 321 |
-
if let Some((o1, o2)) = layout.contiguous_offsets() {
|
| 322 |
-
let dados: &[f32] = cpu.as_slice::<f32>()?;
|
| 323 |
-
// varredura sequencial em blocos de d canais — SEM divisão
|
| 324 |
-
// modular por elemento (k%d custa ~20 ciclos e bloqueia a
|
| 325 |
-
// autovetorização); mesma ordem de comparações por coluna
|
| 326 |
-
// que a referência v0.8.0 ⇒ bits idênticos
|
| 327 |
-
for linha in dados[o1..o2].chunks_exact(d) {
|
| 328 |
-
for (c, &v) in linha.iter().enumerate() {
|
| 329 |
-
let a = v.abs();
|
| 330 |
-
if a > acc[c] {
|
| 331 |
-
acc[c] = a;
|
| 332 |
-
}
|
| 333 |
-
}
|
| 334 |
-
}
|
| 335 |
-
return Ok(());
|
| 336 |
-
}
|
| 337 |
-
}
|
| 338 |
-
// fallback: redução em tensor (backends acelerados / não-contíguo)
|
| 339 |
-
drop(guard);
|
| 340 |
-
let m = t
|
| 341 |
-
.reshape((t.elem_count() / d, d))?
|
| 342 |
-
.abs()?
|
| 343 |
-
.max(0)?
|
| 344 |
-
.clamp(1e-5f32, f32::INFINITY)?;
|
| 345 |
-
for (a, &v) in acc.iter_mut().zip(m.to_vec1::<f32>()?.iter()) {
|
| 346 |
-
if v > *a {
|
| 347 |
-
*a = v;
|
| 348 |
-
}
|
| 349 |
-
}
|
| 350 |
-
Ok(())
|
| 351 |
-
}
|
| 352 |
-
|
| 353 |
/// SmoothQuant (Xiao et al. 2022) — port do `SmoothQuantCompressor`.
|
| 354 |
/// s_j = max|X_j|^α / max|W_j|^(1−α) via EMA; a migração compensada
|
| 355 |
/// (W·diag(s), X/diag(s)) é EXATAMENTE neutra na saída (Teo F-6 — prova
|
|
@@ -370,90 +287,7 @@ impl SuavizadorQuant {
|
|
| 370 |
|
| 371 |
/// Calibração EMA dos máximos por canal de entrada.
|
| 372 |
/// w: (d_out, d_in); x: (N, d_in).
|
| 373 |
-
///
|
| 374 |
-
/// **v0.9.0 (crítica Qwen3.8 nº 2c — veredito NUANCEADO, Teo F-15):**
|
| 375 |
-
/// a crítica apontava a materialização `flatten_all().to_vec1` (cópia
|
| 376 |
-
/// O(|w|+|x|) à RAM) e recomendava reduções de tensor. MEDIÇÃO e
|
| 377 |
-
/// análise do kernel mostraram o contrário na CPU: o `max(0)` do
|
| 378 |
-
/// candle percorre o ramo stride≠1 (cpu_backend/mod.rs:162-182) com
|
| 379 |
-
/// saltos de d_in·4 B — cada leitura é um cache miss; em (4096,512)
|
| 380 |
-
/// a versão "vetorial" é ~11× MAIS LENTA que o laço sequencial
|
| 381 |
-
/// autovetorizável. A solução adotada elimina o problema REAL (a
|
| 382 |
-
/// cópia) mantendo a velocidade: **laço zero-cópia direto no storage**
|
| 383 |
-
/// (`storage_and_layout` + `CpuStorage::as_slice::<f32>`) com
|
| 384 |
-
/// **fallback em tensor** para backends acelerados (GPU/Metal), onde
|
| 385 |
-
/// ops de tensor evitam o round-trip à CPU. Bit-a-bit idêntica à
|
| 386 |
-
/// v0.8.0 em entradas contíguas finitas (teste sq1: mesma sequência
|
| 387 |
-
/// de comparações por acumulador — mesma ordem, mesmos bits).
|
| 388 |
pub fn calibrar(&mut self, w: &Tensor, x: &Tensor) -> Result<()> {
|
| 389 |
-
let d_in = w.dims()[1];
|
| 390 |
-
let mut wmax = vec![1e-5f32; d_in];
|
| 391 |
-
max_abs_por_coluna(w, &mut wmax)?;
|
| 392 |
-
let mut xmax = vec![1e-5f32; d_in];
|
| 393 |
-
if x.dims().len() >= 2 && x.dims().last() == Some(&d_in) {
|
| 394 |
-
max_abs_por_coluna(x, &mut xmax)?;
|
| 395 |
-
}
|
| 396 |
-
// EMA idêntica à v0.8.0: f64 + UMA conversão (igualdade bit-a-bit)
|
| 397 |
-
let ema = |atual: &Option<Vec<f32>>, novo: &[f32]| -> Vec<f32> {
|
| 398 |
-
match atual {
|
| 399 |
-
None => novo.to_vec(),
|
| 400 |
-
Some(a) => a
|
| 401 |
-
.iter()
|
| 402 |
-
.zip(novo.iter())
|
| 403 |
-
.map(|(o, n)| (*o as f64 * self.beta + *n as f64 * (1.0 - self.beta)) as f32)
|
| 404 |
-
.collect(),
|
| 405 |
-
}
|
| 406 |
-
};
|
| 407 |
-
self.w_max = Some(ema(&self.w_max, &wmax));
|
| 408 |
-
self.x_max = Some(ema(&self.x_max, &xmax));
|
| 409 |
-
Ok(())
|
| 410 |
-
}
|
| 411 |
-
|
| 412 |
-
/// **v0.9.0, opt-in:** calibração 100% em ops de tensor (abs/max/clamp)
|
| 413 |
-
/// — indicada em backends ACELERADOS (GPU/Metal), onde evita o
|
| 414 |
-
/// round-trip dos dados à CPU. Na CPU é ~11× mais lenta que o laço
|
| 415 |
-
/// zero-cópia do `calibrar` (kernel stride≠1 do candle — r-34 §3.3);
|
| 416 |
-
/// valores idênticos em entradas finitas (NaN propaga, em vez de ser
|
| 417 |
-
/// ignorado pelo laço — semântica estrita, documentada).
|
| 418 |
-
pub fn calibrar_tensor(&mut self, w: &Tensor, x: &Tensor) -> Result<()> {
|
| 419 |
-
let d_in = w.dims()[1];
|
| 420 |
-
let wmax_t = w.abs()?.max(0)?.clamp(1e-5f32, f32::INFINITY)?;
|
| 421 |
-
let mut wmax = vec![1e-5f32; d_in];
|
| 422 |
-
for (a, &v) in wmax.iter_mut().zip(wmax_t.to_vec1::<f32>()?.iter()) {
|
| 423 |
-
if v > *a {
|
| 424 |
-
*a = v;
|
| 425 |
-
}
|
| 426 |
-
}
|
| 427 |
-
let mut xmax = vec![1e-5f32; d_in];
|
| 428 |
-
if x.dims().len() >= 2 && x.dims().last() == Some(&d_in) {
|
| 429 |
-
let linhas = x.elem_count() / d_in;
|
| 430 |
-
let x2 = x.reshape((linhas, d_in))?;
|
| 431 |
-
let xmax_t = x2.abs()?.max(0)?.clamp(1e-5f32, f32::INFINITY)?;
|
| 432 |
-
for (a, &v) in xmax.iter_mut().zip(xmax_t.to_vec1::<f32>()?.iter()) {
|
| 433 |
-
if v > *a {
|
| 434 |
-
*a = v;
|
| 435 |
-
}
|
| 436 |
-
}
|
| 437 |
-
}
|
| 438 |
-
let ema = |atual: &Option<Vec<f32>>, novo: &[f32]| -> Vec<f32> {
|
| 439 |
-
match atual {
|
| 440 |
-
None => novo.to_vec(),
|
| 441 |
-
Some(a) => a
|
| 442 |
-
.iter()
|
| 443 |
-
.zip(novo.iter())
|
| 444 |
-
.map(|(o, n)| (*o as f64 * self.beta + *n as f64 * (1.0 - self.beta)) as f32)
|
| 445 |
-
.collect(),
|
| 446 |
-
}
|
| 447 |
-
};
|
| 448 |
-
self.w_max = Some(ema(&self.w_max, &wmax));
|
| 449 |
-
self.x_max = Some(ema(&self.x_max, &xmax));
|
| 450 |
-
Ok(())
|
| 451 |
-
}
|
| 452 |
-
|
| 453 |
-
/// **Referência v0.8.0 (oráculo F-15):** calibração por laços nativos
|
| 454 |
-
/// sobre `flatten_all().to_vec1` — O(|w|+|x|) cópias à CPU. Mantida
|
| 455 |
-
/// como oráculo de bit-a-bit e base do benchmark sq2 (pub(crate)).
|
| 456 |
-
pub(crate) fn calibrar_referencia(&mut self, w: &Tensor, x: &Tensor) -> Result<()> {
|
| 457 |
let d_in = w.dims()[1];
|
| 458 |
let mut wmax = vec![1e-5f32; d_in];
|
| 459 |
let wv = w.flatten_all()?.to_vec1::<f32>()?;
|
|
@@ -554,27 +388,6 @@ impl GatePunicao {
|
|
| 554 |
let sig = 1.0 / (1.0 + (-z).exp());
|
| 555 |
(2.0 * sig - 1.0).max(0.0)
|
| 556 |
}
|
| 557 |
-
|
| 558 |
-
/// **v0.9.0 (Aprimoramento 5, forma CORRIGIDA — T-V1/T-V2):** variante
|
| 559 |
-
/// tipada — o domínio de entrada ([0,1]) e de saída ([0,1]) são
|
| 560 |
-
/// garantidos pelo SISTEMA DE TIPOS (construtores validados em
|
| 561 |
-
/// `tipos.rs`), não por comentários. Prova T-V2: 2σ−1 ∈ [−1,1] e o
|
| 562 |
-
/// max(0,·) fecha em [0,1] ⇒ `GateAtivacao::novo` nunca falha para
|
| 563 |
-
/// entrada válida. O caminho quente `valor` (f64 puro) permanece —
|
| 564 |
-
/// a fronteira tipada é para APIs novas e validação de domínio.
|
| 565 |
-
pub fn valor_tipado(&self, punicao: &crate::tipos::Probabilidade) -> crate::tipos::GateAtivacao {
|
| 566 |
-
let g = self.valor(punicao.valor());
|
| 567 |
-
// 2σ(z)−1 ∈ [−1,1] e max(0,·) ⇒ g ∈ [0,1]: o unwrap é PROVADO
|
| 568 |
-
// (T-V2); o debug_assert documentaria violação impossível.
|
| 569 |
-
crate::tipos::GateAtivacao::novo(g).expect("T-V2: g ∈ [0,1] por construção")
|
| 570 |
-
}
|
| 571 |
-
}
|
| 572 |
-
|
| 573 |
-
impl crate::camadas::CamadaHomeostatica for GatePunicao {
|
| 574 |
-
fn gate_ativacao(&self, punicao: &crate::tipos::Probabilidade)
|
| 575 |
-
-> crate::tipos::GateAtivacao {
|
| 576 |
-
self.valor_tipado(punicao)
|
| 577 |
-
}
|
| 578 |
}
|
| 579 |
|
| 580 |
/// Camada FNH — uma hipótese flexível com rotações aprendíveis, suavização de
|
|
@@ -1137,171 +950,4 @@ mod testes {
|
|
| 1137 |
assert!(razao < 0.25, "F-12: razão desvio/distância = {razao}");
|
| 1138 |
Ok(())
|
| 1139 |
}
|
| 1140 |
-
|
| 1141 |
-
/// **F-13 (v0.9.0):** o novo `skew` (index_add, O(n²)) produz A
|
| 1142 |
-
/// bit-a-bit IGUAL à referência v0.8.0 (matmul com seleção densa).
|
| 1143 |
-
///
|
| 1144 |
-
/// *Prova (Teo F-13):* na referência, cada entrada de U é o produto
|
| 1145 |
-
/// interno `θ·S[:,p]` com EXATAMENTE UM termo não-nulo ⇒ soma de θ_k
|
| 1146 |
-
/// com (m−1) zeros — em IEEE-754, x + ±0.0 = x (qualquer ordem). No
|
| 1147 |
-
/// novo, a entrada é `0.0 + θ_k` (index_add) = θ_k. Caso θ_k = −0.0:
|
| 1148 |
-
/// matmul dá (−0.0)+(+0.0) = +0.0 e index_add dá (+0.0)+(−0.0) = +0.0
|
| 1149 |
-
/// — bits idênticos também no zero assinado. A = U − Uᵀ é a MESMA
|
| 1150 |
-
/// operação nas duas versões. ∎ (regressão executável abaixo)
|
| 1151 |
-
#[test]
|
| 1152 |
-
fn f13_skew_index_add_bit_a_bit() -> Result<()> {
|
| 1153 |
-
let dev = Device::Cpu;
|
| 1154 |
-
let mut rng = crate::rng::RngDeterministico::novo(913);
|
| 1155 |
-
for &n in &[2usize, 4, 8, 16, 17] {
|
| 1156 |
-
let r = RotacaoSkew::nova(n, &dev)?;
|
| 1157 |
-
// θ aleatório DENTRO e FORA do clamp (exercita clamp + espelho)
|
| 1158 |
-
let m = n * (n - 1) / 2;
|
| 1159 |
-
let th: Vec<f32> = (0..m)
|
| 1160 |
-
.map(|_| (rng.uniforme_intervalo(-1.4, 1.4)) as f32)
|
| 1161 |
-
.collect();
|
| 1162 |
-
r.theta.set(&Tensor::from_vec(th, (m,), &dev)?)?;
|
| 1163 |
-
let a_novo = r.skew()?;
|
| 1164 |
-
let a_ref = r.skew_matmul_referencia()?;
|
| 1165 |
-
let bn = a_novo.flatten_all()?.to_vec1::<f32>()?;
|
| 1166 |
-
let br = a_ref.flatten_all()?.to_vec1::<f32>()?;
|
| 1167 |
-
assert_eq!(bn.len(), br.len());
|
| 1168 |
-
for (i, (x, y)) in bn.iter().zip(br.iter()).enumerate() {
|
| 1169 |
-
assert_eq!(
|
| 1170 |
-
x.to_bits(),
|
| 1171 |
-
y.to_bits(),
|
| 1172 |
-
"F-13: A[{i}] difere (n={n}): {x} vs {y}"
|
| 1173 |
-
);
|
| 1174 |
-
}
|
| 1175 |
-
// skew-simetria exata preservada: A + Aᵀ = 0 bit-a-bit
|
| 1176 |
-
let soma = (&a_novo + &a_novo.transpose(0, 1)?)?;
|
| 1177 |
-
let sv = soma.flatten_all()?.to_vec1::<f32>()?;
|
| 1178 |
-
assert!(sv.iter().all(|&v| v == 0.0), "F-13: A + Aᵀ ≠ 0");
|
| 1179 |
-
}
|
| 1180 |
-
Ok(())
|
| 1181 |
-
}
|
| 1182 |
-
|
| 1183 |
-
/// **F-14 (v0.9.0):** gradiente flui através do novo skew (a proposta
|
| 1184 |
-
/// Qwen com `to_vec1`+`from_vec` o seccionaria — classe v080-1) E o
|
| 1185 |
-
/// custo assintótico cai: n=256 é INVIÁVEL na referência (S teria
|
| 1186 |
-
/// m·n²·4 B = 8,6 GB) e trivial no novo (n²·4 B = 256 KiB).
|
| 1187 |
-
///
|
| 1188 |
-
/// *Nota honesta:* com L = ‖R‖²_F e θ=0 o gradiente é ZERO por RAZÃO
|
| 1189 |
-
/// MATEMÁTICA (R=I ortogonal ⇒ L = n + ‖A‖²_F + O(θ⁴), mínimo em θ=0
|
| 1190 |
-
/// — A skew tem traço nulo); para exercitar o gradiente usa-se θ≠0
|
| 1191 |
-
/// (perda quadrática) e, no nascimento, perda LINEAR (Σ R_ij), cuja
|
| 1192 |
-
/// derivada 1ª ordem é dΣ/dθ_k = 2 ≠ 0.
|
| 1193 |
-
#[test]
|
| 1194 |
-
fn f14_skew_gradiente_e_escala() -> Result<()> {
|
| 1195 |
-
let dev = Device::Cpu;
|
| 1196 |
-
let n = 16;
|
| 1197 |
-
let r = RotacaoSkew::nova(n, &dev)?;
|
| 1198 |
-
// θ ≠ 0: a perda quadrática tem gradiente 4θ_k + O(θ³) ≠ 0
|
| 1199 |
-
let m = n * (n - 1) / 2;
|
| 1200 |
-
let th: Vec<f32> = (0..m).map(|i| 0.05 + 0.01 * (i % 7) as f32).collect();
|
| 1201 |
-
r.theta.set(&Tensor::from_vec(th, (m,), &dev)?)?;
|
| 1202 |
-
let perda = r.rotacao_cayley()?.sqr()?.sum_all()?;
|
| 1203 |
-
let grads = perda.backward()?;
|
| 1204 |
-
let g = grads
|
| 1205 |
-
.get(&r.theta)
|
| 1206 |
-
.expect("F-14: gradiente deve existir em theta");
|
| 1207 |
-
let gn = g.sqr()?.sum_all()?.to_scalar::<f32>()? as f64;
|
| 1208 |
-
assert!(gn > 0.0, "F-14: gradiente nulo — grafo seccionado (v080-1)");
|
| 1209 |
-
// nascimento (θ=0): perda = ENTRADA ÚNICA R[0,1] — Σ_ij A_ij ≡ 0
|
| 1210 |
-
// para A skew (perda linear global seria cega); dR[0,1]/dθ_0 = 1
|
| 1211 |
-
let r0 = RotacaoSkew::nova(8, &dev)?;
|
| 1212 |
-
let p0 = r0.rotacao_cayley()?.flatten_all()?.narrow(0, 1, 1)?.sum_all()?;
|
| 1213 |
-
let gs0 = p0.backward()?;
|
| 1214 |
-
let g0 = gs0.get(&r0.theta).expect("grad no nascimento");
|
| 1215 |
-
let gn0 = g0.sqr()?.sum_all()?.to_scalar::<f32>()? as f64;
|
| 1216 |
-
assert!(gn0 > 0.0, "F-14: nascimento sem gradiente");
|
| 1217 |
-
// viabilidade em n=256: a referência estouraria 8,6 GB — o novo
|
| 1218 |
-
// constrói A em espaço Θ(n²) sem esforço
|
| 1219 |
-
let r256 = RotacaoSkew::nova(256, &dev)?;
|
| 1220 |
-
let a256 = r256.skew()?;
|
| 1221 |
-
assert_eq!(a256.dims(), [256, 256]);
|
| 1222 |
-
Ok(())
|
| 1223 |
-
}
|
| 1224 |
-
|
| 1225 |
-
/// **sq1 (v0.9.0, Teo F-15):** calibração vetorial bit-a-bit igual à
|
| 1226 |
-
/// referência v0.8.0 em 3 passos EMA (inclui canais sob o piso 1e-5,
|
| 1227 |
-
/// valores grandes e x 3-D). *Prova:* max é seleção exata (sem
|
| 1228 |
-
/// aritmética) e `max(1e-5, max_r |w|) = clamp(≥1e-5) ∘ max` por
|
| 1229 |
-
/// monotonicidade; a EMA é a MESMA expressão f64→f32 nas duas. ∎
|
| 1230 |
-
#[test]
|
| 1231 |
-
fn sq1_calibrar_bit_a_bit() -> Result<()> {
|
| 1232 |
-
let dev = Device::Cpu;
|
| 1233 |
-
let mut rng = crate::rng::RngDeterministico::novo(915);
|
| 1234 |
-
let (d_out, d_in) = (48usize, 32usize);
|
| 1235 |
-
let mut novo = SuavizadorQuant::novo(0.5, 0.98);
|
| 1236 |
-
let mut tens = SuavizadorQuant::novo(0.5, 0.98);
|
| 1237 |
-
let mut ref_ = SuavizadorQuant::novo(0.5, 0.98);
|
| 1238 |
-
for passo in 0..3 {
|
| 1239 |
-
// w com canais deliberadamente sob o piso e valores grandes
|
| 1240 |
-
let mut wv = Vec::with_capacity(d_out * d_in);
|
| 1241 |
-
for r in 0..d_out {
|
| 1242 |
-
for c in 0..d_in {
|
| 1243 |
-
let v = match (r + c) % 7 {
|
| 1244 |
-
0 => 1e-9, // abaixo do piso
|
| 1245 |
-
1 => 12.5, // grande
|
| 1246 |
-
_ => rng.uniforme_intervalo(-2.0, 2.0) as f32, // típico
|
| 1247 |
-
};
|
| 1248 |
-
wv.push(v);
|
| 1249 |
-
}
|
| 1250 |
-
}
|
| 1251 |
-
let w = Tensor::from_vec(wv, (d_out, d_in), &dev)?;
|
| 1252 |
-
// x 3-D no último passo (exercita o reshape), 2-D antes
|
| 1253 |
-
let x = if passo == 2 {
|
| 1254 |
-
let xv: Vec<f32> = (0..16 * 7 * d_in)
|
| 1255 |
-
.map(|_| rng.uniforme_intervalo(-3.0, 3.0) as f32)
|
| 1256 |
-
.collect();
|
| 1257 |
-
Tensor::from_vec(xv, (16, 7, d_in), &dev)?
|
| 1258 |
-
} else {
|
| 1259 |
-
let xv: Vec<f32> = (0..128 * d_in)
|
| 1260 |
-
.map(|_| rng.uniforme_intervalo(-3.0, 3.0) as f32)
|
| 1261 |
-
.collect();
|
| 1262 |
-
Tensor::from_vec(xv, (128, d_in), &dev)?
|
| 1263 |
-
};
|
| 1264 |
-
novo.calibrar(&w, &x)?;
|
| 1265 |
-
tens.calibrar_tensor(&w, &x)?;
|
| 1266 |
-
ref_.calibrar_referencia(&w, &x)?;
|
| 1267 |
-
for (a, b) in novo.w_max.iter().flatten().zip(ref_.w_max.iter().flatten()) {
|
| 1268 |
-
assert_eq!(a.to_bits(), b.to_bits(), "sq1: w_max difere no passo {passo}");
|
| 1269 |
-
}
|
| 1270 |
-
for (a, b) in novo.x_max.iter().flatten().zip(ref_.x_max.iter().flatten()) {
|
| 1271 |
-
assert_eq!(a.to_bits(), b.to_bits(), "sq1: x_max difere no passo {passo}");
|
| 1272 |
-
}
|
| 1273 |
-
// variante tensor (opt-in p/ GPU): mesmos VALORES em entradas finitas
|
| 1274 |
-
for (a, b) in tens.w_max.iter().flatten().zip(ref_.w_max.iter().flatten()) {
|
| 1275 |
-
assert_eq!(a.to_bits(), b.to_bits(), "sq1: w_max(tensor) difere no passo {passo}");
|
| 1276 |
-
}
|
| 1277 |
-
for (a, b) in tens.x_max.iter().flatten().zip(ref_.x_max.iter().flatten()) {
|
| 1278 |
-
assert_eq!(a.to_bits(), b.to_bits(), "sq1: x_max(tensor) difere no passo {passo}");
|
| 1279 |
-
}
|
| 1280 |
-
}
|
| 1281 |
-
// guard: x com última dimensão ≠ d_in não altera o piso de xmax
|
| 1282 |
-
let mut s = SuavizadorQuant::novo(0.5, 0.98);
|
| 1283 |
-
let w = Tensor::randn(0f32, 1f32, (8, d_in), &dev)?;
|
| 1284 |
-
let x_err = Tensor::randn(0f32, 1f32, (8, d_in + 5), &dev)?;
|
| 1285 |
-
s.calibrar(&w, &x_err)?;
|
| 1286 |
-
assert!(s.x_max.as_ref().unwrap().iter().all(|&v| v == 1e-5));
|
| 1287 |
-
// escala finita e positiva (o piso evita 0^(1−α) = ∞ — errata Qwen)
|
| 1288 |
-
s.calibrar(&w, &Tensor::zeros((4, d_in), DType::F32, &dev)?)?;
|
| 1289 |
-
let esc = s.escala();
|
| 1290 |
-
assert!(esc.iter().all(|&v| v.is_finite() && v > 0.0), "sq1: escala não finita");
|
| 1291 |
-
Ok(())
|
| 1292 |
-
}
|
| 1293 |
-
|
| 1294 |
-
/// **sq2 (v0.9.0):** benchmark honesto — a vetorização elimina a cópia
|
| 1295 |
-
/// O(|w|+|x|) à CPU; medido em release pela demo (aqui só smoke).
|
| 1296 |
-
#[test]
|
| 1297 |
-
fn sq2_calibrar_smoke_dimensoes() -> Result<()> {
|
| 1298 |
-
let dev = Device::Cpu;
|
| 1299 |
-
let mut s = SuavizadorQuant::novo(0.5, 0.98);
|
| 1300 |
-
let w = Tensor::randn(0f32, 1f32, (64, 32), &dev)?;
|
| 1301 |
-
let x = Tensor::randn(0f32, 1f32, (256, 32), &dev)?;
|
| 1302 |
-
s.calibrar(&w, &x)?;
|
| 1303 |
-
assert_eq!(s.w_max.as_ref().unwrap().len(), 32);
|
| 1304 |
-
assert_eq!(s.x_max.as_ref().unwrap().len(), 32);
|
| 1305 |
-
Ok(())
|
| 1306 |
-
}
|
| 1307 |
}
|
|
|
|
| 109 |
}
|
| 110 |
|
| 111 |
/// A(θ) skew-simétrica com clamp elemento-a-elemento — construção
|
| 112 |
+
/// DIFERENCIÁVEL por matriz de seleção (matmul): A = U − Uᵀ com U
|
| 113 |
+
/// triangular superior esparsa preenchida por θ (sem slice_assign).
|
| 114 |
+
fn skew(&self) -> Result<Tensor> {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
let dev = self.theta.as_tensor().device();
|
| 116 |
let n = self.n;
|
| 117 |
let m = n * (n - 1) / 2;
|
|
|
|
| 267 |
Ok(par.matmul(&s_mat)?.reshape((n, n))?)
|
| 268 |
}
|
| 269 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
/// SmoothQuant (Xiao et al. 2022) — port do `SmoothQuantCompressor`.
|
| 271 |
/// s_j = max|X_j|^α / max|W_j|^(1−α) via EMA; a migração compensada
|
| 272 |
/// (W·diag(s), X/diag(s)) é EXATAMENTE neutra na saída (Teo F-6 — prova
|
|
|
|
| 287 |
|
| 288 |
/// Calibração EMA dos máximos por canal de entrada.
|
| 289 |
/// w: (d_out, d_in); x: (N, d_in).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
pub fn calibrar(&mut self, w: &Tensor, x: &Tensor) -> Result<()> {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 291 |
let d_in = w.dims()[1];
|
| 292 |
let mut wmax = vec![1e-5f32; d_in];
|
| 293 |
let wv = w.flatten_all()?.to_vec1::<f32>()?;
|
|
|
|
| 388 |
let sig = 1.0 / (1.0 + (-z).exp());
|
| 389 |
(2.0 * sig - 1.0).max(0.0)
|
| 390 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 391 |
}
|
| 392 |
|
| 393 |
/// Camada FNH — uma hipótese flexível com rotações aprendíveis, suavização de
|
|
|
|
| 950 |
assert!(razao < 0.25, "F-12: razão desvio/distância = {razao}");
|
| 951 |
Ok(())
|
| 952 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 953 |
}
|
src/gerador.rs
CHANGED
|
@@ -246,27 +246,18 @@ impl GeradorConceitos {
|
|
| 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 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
/// Recusa checkpoints corrompidos/legados-sem-manifesto (Teo CP-3).
|
| 254 |
-
pub fn carregar_verificado(&mut self, caminho: &str) -> Result<()> {
|
| 255 |
-
let txt = crate::checkpoint::carregar_verificado(caminho)?;
|
| 256 |
-
self.carregar_de_txt(&txt)
|
| 257 |
}
|
| 258 |
|
| 259 |
/// Carrega um checkpoint salvo por `salvar` (mesma ordem de construção
|
| 260 |
/// de `novo` ⇒ índices de variáveis casam 1-a-1 — ajuste fino honesto).
|
| 261 |
-
/// Caminho LEGADO: sem verificação de manifesto (compatibilidade v0.9.0).
|
| 262 |
pub fn carregar(&mut self, caminho: &str) -> Result<()> {
|
| 263 |
let txt = std::fs::read_to_string(caminho)
|
| 264 |
.map_err(|e| candle_core::Error::Msg(format!("io: {e}")))?;
|
| 265 |
-
|
| 266 |
-
}
|
| 267 |
-
|
| 268 |
-
fn carregar_de_txt(&mut self, txt: &str) -> Result<()> {
|
| 269 |
-
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(txt)
|
| 270 |
.map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?;
|
| 271 |
let vars = self.vars();
|
| 272 |
for (i, v) in vars.iter().enumerate() {
|
|
|
|
| 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 |
|
| 255 |
/// Carrega um checkpoint salvo por `salvar` (mesma ordem de construção
|
| 256 |
/// de `novo` ⇒ índices de variáveis casam 1-a-1 — ajuste fino honesto).
|
|
|
|
| 257 |
pub fn carregar(&mut self, caminho: &str) -> Result<()> {
|
| 258 |
let txt = std::fs::read_to_string(caminho)
|
| 259 |
.map_err(|e| candle_core::Error::Msg(format!("io: {e}")))?;
|
| 260 |
+
let obj: serde_json::Map<String, serde_json::Value> = serde_json::from_str(&txt)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
.map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?;
|
| 262 |
let vars = self.vars();
|
| 263 |
for (i, v) in vars.iter().enumerate() {
|
src/jepa.rs
ADDED
|
@@ -0,0 +1,1775 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
/// v0.9.0 — `novo` com RNG determinístico (quita o débito r-33 §8.1).
|
| 68 |
+
/// Mesma distribuição (N(0, s), s = 0,9/√d) — só a FONTE de aleatoriedade
|
| 69 |
+
/// muda: `RngDeterministico` semeado em vez do RNG global do candle.
|
| 70 |
+
pub fn novo_semeado(
|
| 71 |
+
d_entrada: usize,
|
| 72 |
+
d_oculta: usize,
|
| 73 |
+
d_saida: usize,
|
| 74 |
+
dev: &Device,
|
| 75 |
+
rng: &mut RngDeterministico,
|
| 76 |
+
) -> Result<Self> {
|
| 77 |
+
let s1 = (1.0 / (d_entrada as f64).sqrt()) * 0.9;
|
| 78 |
+
let s2 = (1.0 / (d_oculta as f64).sqrt()) * 0.9;
|
| 79 |
+
Ok(Self {
|
| 80 |
+
l1: Linear::sem_vies_semeado(d_entrada, d_oculta, s1, dev, rng)?,
|
| 81 |
+
l2: Linear::sem_vies_semeado(d_oculta, d_saida, s2, dev, rng)?,
|
| 82 |
+
d_entrada,
|
| 83 |
+
d_saida,
|
| 84 |
+
})
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
pub fn fwd(&self, x: &Tensor) -> Result<Tensor> {
|
| 88 |
+
let h = candle_nn::ops::silu(&self.l1.fwd(x)?)?;
|
| 89 |
+
self.l2.fwd(&h)
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
pub fn vars(&self) -> Vec<Var> {
|
| 93 |
+
[self.l1.vars(), self.l2.vars()].concat()
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
/// EMA: `self ← m·self + (1−m)·outro` (usado no codificador-alvo).
|
| 97 |
+
pub fn ema_de(&mut self, outro: &MlpJepa, m: f64) -> Result<()> {
|
| 98 |
+
for (a, b) in self.vars().iter().zip(outro.vars().iter()) {
|
| 99 |
+
let novo = (a.as_tensor().affine(m, 0.0)? + b.as_tensor().affine(1.0 - m, 0.0)?)?;
|
| 100 |
+
a.set(&novo)?;
|
| 101 |
+
}
|
| 102 |
+
Ok(())
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
/// Núcleo JEPA: codificador + preditor + alvo EMA + regularizador VC.
|
| 107 |
+
pub struct NucleoJepa {
|
| 108 |
+
pub encoder: MlpJepa,
|
| 109 |
+
pub preditor: MlpJepa,
|
| 110 |
+
pub alvo: MlpJepa,
|
| 111 |
+
/// tabela de posições (blocos) — entrada extra do preditor.
|
| 112 |
+
pub pos: Var,
|
| 113 |
+
pub d_emb: usize,
|
| 114 |
+
pub d_cond: usize,
|
| 115 |
+
pub m: f64,
|
| 116 |
+
pub lambda_vc: f64,
|
| 117 |
+
pub gamma_vc: f64,
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
impl NucleoJepa {
|
| 121 |
+
pub fn novo(
|
| 122 |
+
d_patch: usize,
|
| 123 |
+
d_emb: usize,
|
| 124 |
+
d_cond: usize,
|
| 125 |
+
n_pos: usize,
|
| 126 |
+
m: f64,
|
| 127 |
+
lambda_vc: f64,
|
| 128 |
+
gamma_vc: f64,
|
| 129 |
+
dev: &Device,
|
| 130 |
+
) -> Result<Self> {
|
| 131 |
+
Self::novo_com_semente(d_patch, d_emb, d_cond, n_pos, m, lambda_vc, gamma_vc, 42, dev)
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
/// v0.9.0 — construção com INICIALIZAÇÃO DETERMINÍSTICA (quita o débito
|
| 135 |
+
/// r-33 §8.1): pesos e tabela de posições nascem de
|
| 136 |
+
/// `RngDeterministico::novo(semente ^ 0x5EED)` — fluxo SEPARADO do RNG de
|
| 137 |
+
/// máscara (`semente`), que permanece intocado. `novo` (acima) delega com
|
| 138 |
+
/// semente fixa 42 para compatibilidade das demos.
|
| 139 |
+
pub fn novo_com_semente(
|
| 140 |
+
d_patch: usize,
|
| 141 |
+
d_emb: usize,
|
| 142 |
+
d_cond: usize,
|
| 143 |
+
n_pos: usize,
|
| 144 |
+
m: f64,
|
| 145 |
+
lambda_vc: f64,
|
| 146 |
+
gamma_vc: f64,
|
| 147 |
+
semente: u64,
|
| 148 |
+
dev: &Device,
|
| 149 |
+
) -> Result<Self> {
|
| 150 |
+
let mut rng = RngDeterministico::novo(semente ^ 0x5EED_u64);
|
| 151 |
+
let encoder = MlpJepa::novo_semeado(d_patch, 2 * d_emb, d_emb, dev, &mut rng)?;
|
| 152 |
+
let preditor = MlpJepa::novo_semeado(d_emb + d_cond + d_emb, 2 * d_emb, d_emb, dev, &mut rng)?;
|
| 153 |
+
// alvo nasce IGUAL ao encoder (ponto de partida do EMA)
|
| 154 |
+
let mut alvo = MlpJepa::novo_semeado(d_patch, 2 * d_emb, d_emb, dev, &mut rng)?;
|
| 155 |
+
for (va, vb) in alvo.vars().iter().zip(encoder.vars().iter()) {
|
| 156 |
+
va.set(vb.as_tensor())?;
|
| 157 |
+
}
|
| 158 |
+
let n_posicoes = n_pos.max(1);
|
| 159 |
+
let pos_dados: Vec<f32> = (0..n_posicoes * d_emb)
|
| 160 |
+
.map(|_| rng.normal(0.0, 0.02) as f32)
|
| 161 |
+
.collect();
|
| 162 |
+
let pos_t = Tensor::from_vec(pos_dados, (n_posicoes, d_emb), dev)?;
|
| 163 |
+
Ok(Self {
|
| 164 |
+
encoder,
|
| 165 |
+
preditor,
|
| 166 |
+
alvo,
|
| 167 |
+
pos: Var::from_tensor(&pos_t)?,
|
| 168 |
+
d_emb,
|
| 169 |
+
d_cond,
|
| 170 |
+
m,
|
| 171 |
+
lambda_vc,
|
| 172 |
+
gamma_vc,
|
| 173 |
+
})
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
pub fn vars(&self) -> Vec<Var> {
|
| 177 |
+
let mut v = self.encoder.vars();
|
| 178 |
+
v.extend(self.preditor.vars());
|
| 179 |
+
v.push(self.pos.clone());
|
| 180 |
+
v // NOTA: alvo NÃO entra — é buffer EMA
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
pub fn ema_passo(&mut self) -> Result<()> {
|
| 184 |
+
self.alvo.ema_de(&self.encoder, self.m)
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
/// Codifica um conjunto de patches (n, d_patch) → (n, d_emb).
|
| 188 |
+
pub fn codificar(&self, patches: &Tensor) -> Result<Tensor> {
|
| 189 |
+
self.encoder.fwd(patches)
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
/// Codifica com o alvo EMA — SEM gradiente (detach por construção: as
|
| 193 |
+
/// vars do alvo não estão no otimizador e o tensor é desconectado).
|
| 194 |
+
pub fn alvo_codificar(&self, patches: &Tensor) -> Result<Tensor> {
|
| 195 |
+
Ok(self.alvo.fwd(patches)?.detach())
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
/// Prediz o embedding do bloco `i_pos` a partir do contexto `z_ctx`
|
| 199 |
+
/// (d_emb) e da condição `cond` (d_cond; zeros se None).
|
| 200 |
+
pub fn predizer(&self, z_ctx: &Tensor, i_pos: usize, cond: Option<&Tensor>) -> Result<Tensor> {
|
| 201 |
+
// z_ctx: (b, d_emb) ou (d_emb,)
|
| 202 |
+
let b = z_ctx.dim(0).unwrap_or(1);
|
| 203 |
+
let dev = z_ctx.device();
|
| 204 |
+
let pos = self.pos.as_tensor().get(i_pos.min(self.pos.dim(0)?.saturating_sub(1)))?;
|
| 205 |
+
let pos_b = pos.broadcast_as((b, self.d_emb))?;
|
| 206 |
+
let cond_b = match cond {
|
| 207 |
+
Some(c) => {
|
| 208 |
+
if c.dims().len() == 1 && b > 1 {
|
| 209 |
+
c.broadcast_as((b, self.d_cond))?
|
| 210 |
+
} else {
|
| 211 |
+
c.clone()
|
| 212 |
+
}
|
| 213 |
+
}
|
| 214 |
+
None => Tensor::zeros((b, self.d_cond), candle_core::DType::F32, dev)?,
|
| 215 |
+
};
|
| 216 |
+
let entrada = Tensor::cat(&[z_ctx, &pos_b, &cond_b], candle_core::D::Minus1)?;
|
| 217 |
+
self.preditor.fwd(&entrada)
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
/// Regularizador variância-covariância do lote de incorporações z (n, d).
|
| 221 |
+
///
|
| 222 |
+
/// **J-1:** no colapso total `z_i = c ∀i` ⇒ `std_j = 0` ⇒ `V = d·γ²`.
|
| 223 |
+
pub fn perda_vc(&self, z: &Tensor) -> Result<Tensor> {
|
| 224 |
+
let n = z.dim(0)? as f64;
|
| 225 |
+
let d = self.d_emb;
|
| 226 |
+
let media = z.mean_keepdim(0)?; // (1, d)
|
| 227 |
+
let centr = z.broadcast_sub(&media)?;
|
| 228 |
+
let var = (centr.sqr()?.sum(0)? / n)?; // (d,)
|
| 229 |
+
let std = var.sqrt()?;
|
| 230 |
+
// dobradiça: max(0, γ − std)²
|
| 231 |
+
let gamma_t =
|
| 232 |
+
Tensor::new(self.gamma_vc as f32, z.device())?.broadcast_as(std.dims())?;
|
| 233 |
+
let gap = gamma_t.broadcast_sub(&std)?.clamp(0.0, f64::INFINITY)?;
|
| 234 |
+
let v = gap.sqr()?.sum_all()?;
|
| 235 |
+
// covariância fora-diagonal: C = centrᵀ·centr/n; perda = ‖C − diag‖²_F/(2d)
|
| 236 |
+
let cov = (centr.transpose(0, 1)?.matmul(¢r)? / n)?; // (d, d)
|
| 237 |
+
let mask_diag = {
|
| 238 |
+
let mut m = vec![0f32; d * d];
|
| 239 |
+
for i in 0..d {
|
| 240 |
+
m[i * d + i] = 1.0;
|
| 241 |
+
}
|
| 242 |
+
Tensor::from_vec(m, (d, d), z.device())?
|
| 243 |
+
};
|
| 244 |
+
let fora = ((&cov * &mask_diag)?.sqr()?.sum_all()? / (2.0 * d.max(1) as f64))?;
|
| 245 |
+
let v_norm = (v / d.max(1) as f64)?;
|
| 246 |
+
Ok((v_norm + fora)?)
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
/// Perda JEPA padrão para UM lote: contexto (número variável de patches),
|
| 250 |
+
/// lista de blocos-alvo (posições), condição opcional.
|
| 251 |
+
///
|
| 252 |
+
/// `z_ctx` = incorporação agregada do contexto (b, d_emb);
|
| 253 |
+
/// `alvos`: para cada elemento do lote, Vec<(i_pos, patches_alvo)>.
|
| 254 |
+
pub fn perda_lote(
|
| 255 |
+
&self,
|
| 256 |
+
z_ctx: &Tensor,
|
| 257 |
+
alvos: &[Vec<(usize, Tensor)>],
|
| 258 |
+
cond: Option<&Tensor>,
|
| 259 |
+
z_ctx_todos: &Tensor,
|
| 260 |
+
) -> Result<(Tensor, f64, f64)> {
|
| 261 |
+
let b = z_ctx.dim(0)?;
|
| 262 |
+
let dev = z_ctx.device();
|
| 263 |
+
let mut perdas: Vec<Tensor> = Vec::new();
|
| 264 |
+
let mut n_alvos = 0usize;
|
| 265 |
+
for (i, blocos) in alvos.iter().enumerate() {
|
| 266 |
+
for (i_pos, patches_alvo) in blocos {
|
| 267 |
+
// ẑ_b = média das incorporações-alvo dos patches do bloco
|
| 268 |
+
let zt = self.alvo_codificar(patches_alvo)?; // (m, d_emb)
|
| 269 |
+
let zt_m = zt.mean(0)?; // (d_emb,)
|
| 270 |
+
let zt_b = zt_m.broadcast_as((1, self.d_emb))?;
|
| 271 |
+
let zc = z_ctx.get(i)?.broadcast_as((1, self.d_emb))?;
|
| 272 |
+
let c_i = cond.map(|c| c.get(i)?.broadcast_as((1, self.d_cond))).transpose()?;
|
| 273 |
+
let pred = self.predizer(&zc, *i_pos, c_i.as_ref())?;
|
| 274 |
+
let dif = (&pred - &zt_b)?;
|
| 275 |
+
let l = (dif.sqr()?.sum_all()? / self.d_emb as f64)?;
|
| 276 |
+
perdas.push(l);
|
| 277 |
+
n_alvos += 1;
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
let l_pred = if perdas.is_empty() {
|
| 281 |
+
Tensor::zeros((), candle_core::DType::F32, &dev)?
|
| 282 |
+
} else {
|
| 283 |
+
(Tensor::stack(&perdas, 0)?.sum_all()? / n_alvos.max(1) as f64)?
|
| 284 |
+
};
|
| 285 |
+
let l_vc = self.perda_vc(z_ctx_todos)?;
|
| 286 |
+
let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(self.lambda_vc, 0.0)?)?;
|
| 287 |
+
let lp = l_pred.to_scalar::<f32>()? as f64;
|
| 288 |
+
let lv = l_vc.to_scalar::<f32>()? as f64;
|
| 289 |
+
Ok((total, lp, lv))
|
| 290 |
+
}
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
// ---------------------------------------------------------------------------
|
| 294 |
+
// Parte 2 — máscaras
|
| 295 |
+
// ---------------------------------------------------------------------------
|
| 296 |
+
|
| 297 |
+
/// Máscara de blocos 2D (Image-JEPA): grade (h, w) de patches; sorteia
|
| 298 |
+
/// `n_blocos` retângulos aleatórios com fração alvo `frac` de patches.
|
| 299 |
+
pub fn mascara_blocos_2d(h: usize, w: usize, n_blocos: usize, frac: f64, rng: &mut RngDeterministico) -> Vec<bool> {
|
| 300 |
+
let total = h * w;
|
| 301 |
+
let alvo = (total as f64 * frac) as usize;
|
| 302 |
+
let mut masc = vec![false; total];
|
| 303 |
+
let mut coberto = 0usize;
|
| 304 |
+
for _ in 0..n_blocos.max(1) {
|
| 305 |
+
if coberto >= alvo {
|
| 306 |
+
break;
|
| 307 |
+
}
|
| 308 |
+
let bh = 1 + rng.inteiro(h.min(4));
|
| 309 |
+
let bw = 1 + rng.inteiro(w.min(4));
|
| 310 |
+
let y0 = rng.inteiro(h + 1 - bh);
|
| 311 |
+
let x0 = rng.inteiro(w + 1 - bw);
|
| 312 |
+
for y in y0..y0 + bh {
|
| 313 |
+
for x in x0..x0 + bw {
|
| 314 |
+
let i = y * w + x;
|
| 315 |
+
if !masc[i] {
|
| 316 |
+
masc[i] = true;
|
| 317 |
+
coberto += 1;
|
| 318 |
+
}
|
| 319 |
+
}
|
| 320 |
+
}
|
| 321 |
+
}
|
| 322 |
+
masc
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
/// Máscara de tubos 3D (V-JEPA 2): grade (t, h, w); tubos = retângulos que
|
| 326 |
+
/// atravessam o tempo.
|
| 327 |
+
pub fn mascara_tubos_3d(t: usize, h: usize, w: usize, n_tubos: usize, frac: f64, rng: &mut RngDeterministico) -> Vec<bool> {
|
| 328 |
+
let total = t * h * w;
|
| 329 |
+
let alvo = (total as f64 * frac) as usize;
|
| 330 |
+
let mut masc = vec![false; total];
|
| 331 |
+
let mut coberto = 0usize;
|
| 332 |
+
for _ in 0..n_tubos.max(1) {
|
| 333 |
+
if coberto >= alvo {
|
| 334 |
+
break;
|
| 335 |
+
}
|
| 336 |
+
let bh = 1 + rng.inteiro(h.min(3));
|
| 337 |
+
let bw = 1 + rng.inteiro(w.min(3));
|
| 338 |
+
let y0 = rng.inteiro(h + 1 - bh);
|
| 339 |
+
let x0 = rng.inteiro(w + 1 - bw);
|
| 340 |
+
for tt in 0..t {
|
| 341 |
+
for y in y0..y0 + bh {
|
| 342 |
+
for x in x0..x0 + bw {
|
| 343 |
+
let i = (tt * h + y) * w + x;
|
| 344 |
+
if !masc[i] {
|
| 345 |
+
masc[i] = true;
|
| 346 |
+
coberto += 1;
|
| 347 |
+
}
|
| 348 |
+
}
|
| 349 |
+
}
|
| 350 |
+
}
|
| 351 |
+
}
|
| 352 |
+
masc
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
/// Máscara de bandas (Audio-JEPA): grade (f, tau) — bandas de frequência
|
| 356 |
+
/// horizontais com extensão temporal.
|
| 357 |
+
pub fn mascara_bandas(f: usize, tau: usize, n_bandas: usize, frac: f64, rng: &mut RngDeterministico) -> Vec<bool> {
|
| 358 |
+
mascara_blocos_2d(f, tau, n_bandas, frac, rng) // blocos na grade (f, tau)
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
/// Máscara de blocos 3D (3D-JEPA): grade (x, y, z) de voxels.
|
| 362 |
+
pub fn mascara_blocos_3d(x: usize, y: usize, z: usize, n_blocos: usize, frac: f64, rng: &mut RngDeterministico) -> Vec<bool> {
|
| 363 |
+
mascara_tubos_3d(x, y, z, n_blocos, frac, rng)
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
/// Máscara de segmentos canal×tempo (Brain-JEPA): canais inteiros OU
|
| 367 |
+
/// janelas temporais (aleatório por chamada).
|
| 368 |
+
pub fn mascara_segmentos(c: usize, t: usize, frac: f64, rng: &mut RngDeterministico) -> Vec<bool> {
|
| 369 |
+
let total = c * t;
|
| 370 |
+
let alvo = (total as f64 * frac) as usize;
|
| 371 |
+
let mut masc = vec![false; total];
|
| 372 |
+
if rng.inteiro(2) == 0 {
|
| 373 |
+
// canais inteiros
|
| 374 |
+
let mut coberto = 0;
|
| 375 |
+
let mut canais: Vec<usize> = (0..c).collect();
|
| 376 |
+
// embaralhar
|
| 377 |
+
for i in (1..canais.len()).rev() {
|
| 378 |
+
let j = rng.inteiro(i + 1);
|
| 379 |
+
canais.swap(i, j);
|
| 380 |
+
}
|
| 381 |
+
for &ch in canais.iter() {
|
| 382 |
+
if coberto >= alvo {
|
| 383 |
+
break;
|
| 384 |
+
}
|
| 385 |
+
for tt in 0..t {
|
| 386 |
+
masc[ch * t + tt] = true;
|
| 387 |
+
coberto += 1;
|
| 388 |
+
}
|
| 389 |
+
}
|
| 390 |
+
} else {
|
| 391 |
+
// janelas temporais
|
| 392 |
+
let mut coberto = 0;
|
| 393 |
+
let mut t0 = 0;
|
| 394 |
+
while coberto < alvo && t0 < t {
|
| 395 |
+
let len = 1 + rng.inteiro((t / 4).max(1));
|
| 396 |
+
for ch in 0..c {
|
| 397 |
+
for tt in t0..(t0 + len).min(t) {
|
| 398 |
+
masc[ch * t + tt] = true;
|
| 399 |
+
coberto += 1;
|
| 400 |
+
}
|
| 401 |
+
}
|
| 402 |
+
t0 += len + 1;
|
| 403 |
+
}
|
| 404 |
+
}
|
| 405 |
+
masc
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
/// Máscara 1D (T-JEPA): intervalos contíguos em n posições.
|
| 409 |
+
pub fn mascara_intervalos_1d(n: usize, frac: f64, rng: &mut RngDeterministico) -> Vec<bool> {
|
| 410 |
+
let alvo = (n as f64 * frac) as usize;
|
| 411 |
+
let mut masc = vec![false; n];
|
| 412 |
+
let mut coberto = 0;
|
| 413 |
+
while coberto < alvo {
|
| 414 |
+
let len = 1 + rng.inteiro((n / 8).max(1));
|
| 415 |
+
let i0 = rng.inteiro(n);
|
| 416 |
+
for i in i0..(i0 + len).min(n) {
|
| 417 |
+
if !masc[i] {
|
| 418 |
+
masc[i] = true;
|
| 419 |
+
coberto += 1;
|
| 420 |
+
}
|
| 421 |
+
}
|
| 422 |
+
}
|
| 423 |
+
masc
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
/// Amostragem dos pontos mais distantes (FPS) para Point-JEPA.
|
| 427 |
+
pub fn pontos_mais_distantes(pontos: &[Vec<f64>], k: usize, rng: &mut RngDeterministico) -> Vec<usize> {
|
| 428 |
+
let n = pontos.len();
|
| 429 |
+
if n == 0 || k == 0 {
|
| 430 |
+
return vec![];
|
| 431 |
+
}
|
| 432 |
+
let k = k.min(n);
|
| 433 |
+
let mut centros = vec![rng.inteiro(n)];
|
| 434 |
+
let mut dist_min: Vec<f64> = pontos
|
| 435 |
+
.iter()
|
| 436 |
+
.map(|p| distancia_eucl(p, &pontos[centros[0]]))
|
| 437 |
+
.collect();
|
| 438 |
+
while centros.len() < k {
|
| 439 |
+
let prox = dist_min
|
| 440 |
+
.iter()
|
| 441 |
+
.enumerate()
|
| 442 |
+
.filter(|(i, _)| !centros.contains(i))
|
| 443 |
+
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
|
| 444 |
+
.map(|(i, _)| i)
|
| 445 |
+
.unwrap_or(0);
|
| 446 |
+
centros.push(prox);
|
| 447 |
+
for (i, p) in pontos.iter().enumerate() {
|
| 448 |
+
let d = distancia_eucl(p, &pontos[prox]);
|
| 449 |
+
if d < dist_min[i] {
|
| 450 |
+
dist_min[i] = d;
|
| 451 |
+
}
|
| 452 |
+
}
|
| 453 |
+
}
|
| 454 |
+
centros
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
fn distancia_eucl(a: &[f64], b: &[f64]) -> f64 {
|
| 458 |
+
a.iter().zip(b).map(|(x, y)| (x - y) * (x - y)).sum::<f64>().sqrt()
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
// ---------------------------------------------------------------------------
|
| 462 |
+
// Parte 3 — adaptadores de modalidade
|
| 463 |
+
// ---------------------------------------------------------------------------
|
| 464 |
+
|
| 465 |
+
/// (C, H, W) → patches (H/ph · W/pw, C·ph·pw) — Image-JEPA.
|
| 466 |
+
pub fn em_patches_2d(img: &Tensor, ph: usize, pw: usize) -> Result<Tensor> {
|
| 467 |
+
let (c, h, w) = (img.dim(0)?, img.dim(1)?, img.dim(2)?);
|
| 468 |
+
let dev = img.device();
|
| 469 |
+
let mut linhas: Vec<Tensor> = Vec::new();
|
| 470 |
+
for y in (0..h).step_by(ph) {
|
| 471 |
+
for x in (0..w).step_by(pw) {
|
| 472 |
+
let hh = ph.min(h - y);
|
| 473 |
+
let ww = pw.min(w - x);
|
| 474 |
+
let patch = img.narrow(0, 0, c)?.narrow(1, y, hh)?.narrow(2, x, ww)?;
|
| 475 |
+
// padding para (C, ph, pw) se borda
|
| 476 |
+
let patch = if hh < ph || ww < pw {
|
| 477 |
+
let mut buf = patch.flatten_all()?.to_vec1::<f32>()?;
|
| 478 |
+
let falta = c * ph * pw - buf.len();
|
| 479 |
+
buf.extend(std::iter::repeat(0f32).take(falta));
|
| 480 |
+
Tensor::from_vec(buf, (c * ph * pw,), &dev)?
|
| 481 |
+
} else {
|
| 482 |
+
patch.flatten_all()?
|
| 483 |
+
};
|
| 484 |
+
linhas.push(patch);
|
| 485 |
+
}
|
| 486 |
+
}
|
| 487 |
+
Ok(Tensor::stack(&linhas, 0)?)
|
| 488 |
+
}
|
| 489 |
+
|
| 490 |
+
/// (T, H, W) → tubos (T/pt · H/ph · W/pw, pt·ph·pw) — V-JEPA 2.
|
| 491 |
+
pub fn em_tubos_3d(vid: &Tensor, pt: usize, ph: usize, pw: usize) -> Result<Tensor> {
|
| 492 |
+
let (t, h, w) = (vid.dim(0)?, vid.dim(1)?, vid.dim(2)?);
|
| 493 |
+
let dev = vid.device();
|
| 494 |
+
let mut linhas: Vec<Tensor> = Vec::new();
|
| 495 |
+
for tt in (0..t).step_by(pt) {
|
| 496 |
+
let tl = pt.min(t - tt);
|
| 497 |
+
for y in (0..h).step_by(ph) {
|
| 498 |
+
for x in (0..w).step_by(pw) {
|
| 499 |
+
let hh = ph.min(h - y);
|
| 500 |
+
let ww = pw.min(w - x);
|
| 501 |
+
let tubo = vid
|
| 502 |
+
.narrow(0, tt, tl)?
|
| 503 |
+
.narrow(1, y, hh)?
|
| 504 |
+
.narrow(2, x, ww)?
|
| 505 |
+
.flatten_all()?;
|
| 506 |
+
let mut buf = tubo.to_vec1::<f32>()?;
|
| 507 |
+
let falta = pt * ph * pw - buf.len();
|
| 508 |
+
buf.extend(std::iter::repeat(0f32).take(falta));
|
| 509 |
+
linhas.push(Tensor::from_vec(buf, (pt * ph * pw,), &dev)?);
|
| 510 |
+
}
|
| 511 |
+
}
|
| 512 |
+
}
|
| 513 |
+
Ok(Tensor::stack(&linhas, 0)?)
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
/// (F, τ) → patches (F/pf · τ/pt, pf·pt) — Audio-JEPA.
|
| 517 |
+
pub fn em_patches_espectrograma(spec: &Tensor, pf: usize, pt: usize) -> Result<Tensor> {
|
| 518 |
+
let (f, tau) = (spec.dim(0)?, spec.dim(1)?);
|
| 519 |
+
let dev = spec.device();
|
| 520 |
+
let mut linhas: Vec<Tensor> = Vec::new();
|
| 521 |
+
for i in (0..f).step_by(pf) {
|
| 522 |
+
for j in (0..tau).step_by(pt) {
|
| 523 |
+
let ff = pf.min(f - i);
|
| 524 |
+
let tt = pt.min(tau - j);
|
| 525 |
+
let p = spec.narrow(0, i, ff)?.narrow(1, j, tt)?.flatten_all()?;
|
| 526 |
+
let mut buf = p.to_vec1::<f32>()?;
|
| 527 |
+
let falta = pf * pt - buf.len();
|
| 528 |
+
buf.extend(std::iter::repeat(0f32).take(falta));
|
| 529 |
+
linhas.push(Tensor::from_vec(buf, (pf * pt,), &dev)?);
|
| 530 |
+
}
|
| 531 |
+
}
|
| 532 |
+
Ok(Tensor::stack(&linhas, 0)?)
|
| 533 |
+
}
|
| 534 |
+
|
| 535 |
+
/// (C, T) → patches canal-independentes (C · T/pt, pt) — T-JEPA/Brain-JEPA.
|
| 536 |
+
pub fn em_patches_serie(ts: &Tensor, pt: usize) -> Result<Tensor> {
|
| 537 |
+
let (c, t) = (ts.dim(0)?, ts.dim(1)?);
|
| 538 |
+
let dev = ts.device();
|
| 539 |
+
let mut linhas: Vec<Tensor> = Vec::new();
|
| 540 |
+
for ch in 0..c {
|
| 541 |
+
for j in (0..t).step_by(pt) {
|
| 542 |
+
let tt = pt.min(t - j);
|
| 543 |
+
let p = ts.narrow(0, ch, 1)?.narrow(1, j, tt)?.flatten_all()?;
|
| 544 |
+
let mut buf = p.to_vec1::<f32>()?;
|
| 545 |
+
let falta = pt - buf.len();
|
| 546 |
+
buf.extend(std::iter::repeat(0f32).take(falta));
|
| 547 |
+
linhas.push(Tensor::from_vec(buf, (pt,), &dev)?);
|
| 548 |
+
}
|
| 549 |
+
}
|
| 550 |
+
Ok(Tensor::stack(&linhas, 0)?)
|
| 551 |
+
}
|
| 552 |
+
|
| 553 |
+
/// (X, Y, Z) → voxels (X/px · Y/py · Z/pz, px·py·pz) — 3D-JEPA.
|
| 554 |
+
pub fn em_voxels(vol: &Tensor, px: usize, py: usize, pz: usize) -> Result<Tensor> {
|
| 555 |
+
let (x, y, z) = (vol.dim(0)?, vol.dim(1)?, vol.dim(2)?);
|
| 556 |
+
let dev = vol.device();
|
| 557 |
+
let mut linhas: Vec<Tensor> = Vec::new();
|
| 558 |
+
for i in (0..x).step_by(px) {
|
| 559 |
+
for j in (0..y).step_by(py) {
|
| 560 |
+
for k in (0..z).step_by(pz) {
|
| 561 |
+
let lx = px.min(x - i);
|
| 562 |
+
let ly = py.min(y - j);
|
| 563 |
+
let lz = pz.min(z - k);
|
| 564 |
+
let p = vol.narrow(0, i, lx)?.narrow(1, j, ly)?.narrow(2, k, lz)?.flatten_all()?;
|
| 565 |
+
let mut buf = p.to_vec1::<f32>()?;
|
| 566 |
+
let falta = px * py * pz - buf.len();
|
| 567 |
+
buf.extend(std::iter::repeat(0f32).take(falta));
|
| 568 |
+
linhas.push(Tensor::from_vec(buf, (px * py * pz,), &dev)?);
|
| 569 |
+
}
|
| 570 |
+
}
|
| 571 |
+
}
|
| 572 |
+
Ok(Tensor::stack(&linhas, 0)?)
|
| 573 |
+
}
|
| 574 |
+
|
| 575 |
+
/// Nuvem de pontos (n, d) → grupos FPS (k, m·d): k centros por FPS, cada
|
| 576 |
+
/// grupo = os m vizinhos mais próximos do centro (concatenados) — Point-JEPA.
|
| 577 |
+
pub fn em_grupos_pontos(pc: &Tensor, k: usize, m: usize, rng: &mut RngDeterministico) -> Result<Tensor> {
|
| 578 |
+
let n = pc.dim(0)?;
|
| 579 |
+
let d = pc.dim(1)?;
|
| 580 |
+
let coords = pc.to_vec2::<f32>()?;
|
| 581 |
+
let coords_f: Vec<Vec<f64>> = coords.iter().map(|r| r.iter().map(|&v| v as f64).collect()).collect();
|
| 582 |
+
let centros = pontos_mais_distantes(&coords_f, k, rng);
|
| 583 |
+
let dev = pc.device();
|
| 584 |
+
let mut linhas: Vec<Tensor> = Vec::new();
|
| 585 |
+
for &ci in ¢ros {
|
| 586 |
+
// m vizinhos mais próximos do centro ci (incluindo ele próprio)
|
| 587 |
+
let mut ord: Vec<usize> = (0..n).collect();
|
| 588 |
+
ord.sort_by(|&a, &b| {
|
| 589 |
+
let da = distancia_eucl(&coords_f[a], &coords_f[ci]);
|
| 590 |
+
let db = distancia_eucl(&coords_f[b], &coords_f[ci]);
|
| 591 |
+
da.partial_cmp(&db).unwrap()
|
| 592 |
+
});
|
| 593 |
+
let mut buf: Vec<f32> = Vec::with_capacity(m * d);
|
| 594 |
+
for &idx in ord.iter().take(m) {
|
| 595 |
+
for j in 0..d {
|
| 596 |
+
buf.push(coords[idx][j]);
|
| 597 |
+
}
|
| 598 |
+
}
|
| 599 |
+
let falta = m * d - buf.len();
|
| 600 |
+
buf.extend(std::iter::repeat(0f32).take(falta));
|
| 601 |
+
linhas.push(Tensor::from_vec(buf, (m * d,), &dev)?);
|
| 602 |
+
}
|
| 603 |
+
Ok(Tensor::stack(&linhas, 0)?)
|
| 604 |
+
}
|
| 605 |
+
|
| 606 |
+
// ---------------------------------------------------------------------------
|
| 607 |
+
// Parte 4 — variantes e treino unificado
|
| 608 |
+
// ---------------------------------------------------------------------------
|
| 609 |
+
|
| 610 |
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
| 611 |
+
pub enum VarianteJepa {
|
| 612 |
+
ImageJepa,
|
| 613 |
+
VJepa2,
|
| 614 |
+
McJepa,
|
| 615 |
+
AudioJepa,
|
| 616 |
+
PointJepa,
|
| 617 |
+
Jepa3D,
|
| 618 |
+
BrainJepa,
|
| 619 |
+
TJepa,
|
| 620 |
+
LeJepa,
|
| 621 |
+
BJepa,
|
| 622 |
+
}
|
| 623 |
+
|
| 624 |
+
impl VarianteJepa {
|
| 625 |
+
pub fn nome(&self) -> &'static str {
|
| 626 |
+
match self {
|
| 627 |
+
Self::ImageJepa => "Image-JEPA",
|
| 628 |
+
Self::VJepa2 => "V-JEPA 2",
|
| 629 |
+
Self::McJepa => "MC-JEPA",
|
| 630 |
+
Self::AudioJepa => "Audio-JEPA",
|
| 631 |
+
Self::PointJepa => "Point-JEPA",
|
| 632 |
+
Self::Jepa3D => "3D-JEPA",
|
| 633 |
+
Self::BrainJepa => "Brain-JEPA",
|
| 634 |
+
Self::TJepa => "T-JEPA",
|
| 635 |
+
Self::LeJepa => "LeJEPA",
|
| 636 |
+
Self::BJepa => "BJEPA",
|
| 637 |
+
}
|
| 638 |
+
}
|
| 639 |
+
}
|
| 640 |
+
|
| 641 |
+
#[derive(Debug, Clone)]
|
| 642 |
+
pub struct CfgJepa {
|
| 643 |
+
pub d_emb: usize,
|
| 644 |
+
pub epocas: usize,
|
| 645 |
+
pub lr: f64,
|
| 646 |
+
pub m: f64, // EMA
|
| 647 |
+
pub lambda_vc: f64,
|
| 648 |
+
pub gamma_vc: f64,
|
| 649 |
+
pub frac_mascara: f64,
|
| 650 |
+
pub semente: u64,
|
| 651 |
+
/// tamanho do bloco-alvo (nº de patches por bloco).
|
| 652 |
+
pub tam_bloco: usize,
|
| 653 |
+
}
|
| 654 |
+
|
| 655 |
+
impl Default for CfgJepa {
|
| 656 |
+
fn default() -> Self {
|
| 657 |
+
Self {
|
| 658 |
+
d_emb: 32,
|
| 659 |
+
epocas: 60,
|
| 660 |
+
lr: 1.5e-3,
|
| 661 |
+
m: 0.99,
|
| 662 |
+
lambda_vc: 0.5,
|
| 663 |
+
gamma_vc: 1.0,
|
| 664 |
+
frac_mascara: 0.35,
|
| 665 |
+
semente: 42,
|
| 666 |
+
tam_bloco: 2,
|
| 667 |
+
}
|
| 668 |
+
}
|
| 669 |
+
}
|
| 670 |
+
|
| 671 |
+
/// Relatório de treino de uma variante.
|
| 672 |
+
#[derive(Debug, Clone)]
|
| 673 |
+
pub struct RelatorioJepa {
|
| 674 |
+
pub variante: String,
|
| 675 |
+
pub perda_inicial: f64,
|
| 676 |
+
pub perda_final: f64,
|
| 677 |
+
/// perda PREDITIVA isolada (sem o regularizador VC) — critério de
|
| 678 |
+
/// aprendizado honesto (a VC pode crescer legitimamente ao empurrar o
|
| 679 |
+
/// std das incorporações para cima).
|
| 680 |
+
pub perda_pred_inicial: f64,
|
| 681 |
+
pub perda_pred_final: f64,
|
| 682 |
+
/// desvio-padrão médio por dimensão das incorporações (monitor de
|
| 683 |
+
/// colapso: → 0 = colapso).
|
| 684 |
+
pub std_emb_final: f64,
|
| 685 |
+
pub std_emb_inicial: f64,
|
| 686 |
+
pub n_patches: usize,
|
| 687 |
+
pub d_patch: usize,
|
| 688 |
+
pub epocas: usize,
|
| 689 |
+
}
|
| 690 |
+
|
| 691 |
+
/// Treina uma variante JEPA sobre patches já extraídos `(n, d_patch)` com
|
| 692 |
+
/// um esquema de máscara fornecido (padrão: intervalos 1D).
|
| 693 |
+
///
|
| 694 |
+
/// Máscara=true ⇒ patch é ALVO (bloco); false ⇒ CONTEXTO.
|
| 695 |
+
pub fn treinar_variante(
|
| 696 |
+
patches: &Tensor,
|
| 697 |
+
mascara: &dyn Fn(usize, &mut RngDeterministico) -> Vec<bool>,
|
| 698 |
+
cfg: &CfgJepa,
|
| 699 |
+
variante: VarianteJepa,
|
| 700 |
+
cond: Option<&Tensor>,
|
| 701 |
+
dev: &Device,
|
| 702 |
+
) -> Result<RelatorioJepa> {
|
| 703 |
+
let n = patches.dim(0)?;
|
| 704 |
+
let d_patch = patches.dim(1)?;
|
| 705 |
+
let mut rng = RngDeterministico::novo(cfg.semente);
|
| 706 |
+
let mut nucleo = NucleoJepa::novo_com_semente(
|
| 707 |
+
d_patch,
|
| 708 |
+
cfg.d_emb,
|
| 709 |
+
if cond.is_some() { cond.unwrap().dim(1)? } else { 0 },
|
| 710 |
+
n,
|
| 711 |
+
cfg.m,
|
| 712 |
+
cfg.lambda_vc,
|
| 713 |
+
cfg.gamma_vc,
|
| 714 |
+
cfg.semente,
|
| 715 |
+
dev,
|
| 716 |
+
)?;
|
| 717 |
+
let otimo_vars = nucleo.vars();
|
| 718 |
+
let mut otimizador = crate::treino::AdamWManual::novo(otimo_vars, cfg.lr, 0.01);
|
| 719 |
+
|
| 720 |
+
// métricas iniciais
|
| 721 |
+
let z0 = nucleo.codificar(patches)?;
|
| 722 |
+
let std0 = std_por_dim(&z0)?;
|
| 723 |
+
|
| 724 |
+
let mut perda_inicial = f64::NAN;
|
| 725 |
+
let mut perda_final = f64::NAN;
|
| 726 |
+
let mut pred_inicial = f64::NAN;
|
| 727 |
+
let mut pred_final = f64::NAN;
|
| 728 |
+
let mut vc_final = f64::NAN;
|
| 729 |
+
for epoca in 0..cfg.epocas {
|
| 730 |
+
// máscara fresca a cada época (com proteção contra máscara degenerada:
|
| 731 |
+
// se contexto ou alvo ficar vazio, repete com fração menor — a época
|
| 732 |
+
// NUNCA é pulada, a perda sempre é computada)
|
| 733 |
+
let (ctx_idx, blocos) = loop {
|
| 734 |
+
let mut masc = mascara(n, &mut rng);
|
| 735 |
+
// defensivo: máscara menor que n => o resto é contexto
|
| 736 |
+
if masc.len() < n {
|
| 737 |
+
masc.resize(n, false);
|
| 738 |
+
}
|
| 739 |
+
masc.truncate(n);
|
| 740 |
+
let ctx: Vec<u32> = (0..n).filter(|&i| !masc[i]).map(|i| i as u32).collect();
|
| 741 |
+
let alvos: Vec<usize> = (0..n).filter(|&i| masc[i]).collect();
|
| 742 |
+
if !ctx.is_empty() && !alvos.is_empty() {
|
| 743 |
+
break (ctx, alvos);
|
| 744 |
+
}
|
| 745 |
+
};
|
| 746 |
+
// agrupa alvos em blocos de tam_bloco
|
| 747 |
+
let grupos: Vec<Vec<usize>> = blocos
|
| 748 |
+
.chunks(cfg.tam_bloco.max(1))
|
| 749 |
+
.map(|c| c.to_vec())
|
| 750 |
+
.collect();
|
| 751 |
+
if ctx_idx.is_empty() || grupos.is_empty() {
|
| 752 |
+
continue;
|
| 753 |
+
}
|
| 754 |
+
let dev2 = patches.device();
|
| 755 |
+
let n_ctx = ctx_idx.len();
|
| 756 |
+
let idx_t = Tensor::from_vec(ctx_idx, (n_ctx,), &dev2)?;
|
| 757 |
+
let ctx_patches = patches.index_select(&idx_t, 0)?;
|
| 758 |
+
let z_ctx_todos = nucleo.codificar(&ctx_patches)?; // (n_ctx, d_emb)
|
| 759 |
+
let z_ctx = z_ctx_todos.mean_keepdim(0)?; // (1, d_emb)
|
| 760 |
+
// alvos por grupo
|
| 761 |
+
let mut alvos: Vec<Vec<(usize, Tensor)>> = Vec::new();
|
| 762 |
+
for g in &grupos {
|
| 763 |
+
let idx: Vec<u32> = g.iter().map(|&i| i as u32).collect();
|
| 764 |
+
let n_g = idx.len();
|
| 765 |
+
let it = Tensor::from_vec(idx, (n_g,), &dev2)?;
|
| 766 |
+
alvos.push(vec![(g[0], patches.index_select(&it, 0)?)]);
|
| 767 |
+
}
|
| 768 |
+
// condição por amostra — broadcast (o lote é 1 aqui: o conjunto todo)
|
| 769 |
+
let (perda, lp, lv) = nucleo.perda_lote(&z_ctx, &[alvos_concat(&alvos)], cond, &z_ctx_todos)?;
|
| 770 |
+
let p = perda.to_scalar::<f32>()? as f64;
|
| 771 |
+
if p.is_finite() {
|
| 772 |
+
if epoca == 0 {
|
| 773 |
+
perda_inicial = p;
|
| 774 |
+
pred_inicial = lp;
|
| 775 |
+
}
|
| 776 |
+
perda_final = p;
|
| 777 |
+
pred_final = lp;
|
| 778 |
+
vc_final = lv;
|
| 779 |
+
}
|
| 780 |
+
// decaimento cosseno do lr + clipping de gradiente (estabilidade)
|
| 781 |
+
let frac = epoca as f64 / cfg.epocas.max(1) as f64;
|
| 782 |
+
let lr_t = cfg.lr * 0.5 * (1.0 + (std::f64::consts::PI * frac).cos());
|
| 783 |
+
otimizador.set_lr(lr_t);
|
| 784 |
+
let mut grads = crate::treino::grads_achatados(&perda, &nucleo.vars())?;
|
| 785 |
+
crate::treino::AdamWManual::clipar(&mut grads, 1.0);
|
| 786 |
+
otimizador.passo(grads);
|
| 787 |
+
nucleo.ema_passo()?;
|
| 788 |
+
}
|
| 789 |
+
let z1 = nucleo.codificar(patches)?;
|
| 790 |
+
let std1 = std_por_dim(&z1)?;
|
| 791 |
+
let _ = (pred_inicial, vc_final);
|
| 792 |
+
Ok(RelatorioJepa {
|
| 793 |
+
variante: variante.nome().to_string(),
|
| 794 |
+
perda_inicial,
|
| 795 |
+
perda_final,
|
| 796 |
+
perda_pred_inicial: pred_inicial,
|
| 797 |
+
perda_pred_final: pred_final,
|
| 798 |
+
std_emb_final: std1,
|
| 799 |
+
std_emb_inicial: std0,
|
| 800 |
+
n_patches: n,
|
| 801 |
+
d_patch,
|
| 802 |
+
epocas: cfg.epocas,
|
| 803 |
+
})
|
| 804 |
+
}
|
| 805 |
+
|
| 806 |
+
fn alvos_concat(alvos: &[Vec<(usize, Tensor)>]) -> Vec<(usize, Tensor)> {
|
| 807 |
+
alvos.iter().flatten().cloned().collect()
|
| 808 |
+
}
|
| 809 |
+
|
| 810 |
+
// ---------------------------------------------------------------------------
|
| 811 |
+
// v0.9.0 — fração de máscara ADAPTATIVA por Thompson Sampling
|
| 812 |
+
// (crítica Qwen3.8 C; provas T-J1/T-J2 em r-34 §7)
|
| 813 |
+
// ---------------------------------------------------------------------------
|
| 814 |
+
|
| 815 |
+
/// Relatório do treino com fração adaptativa.
|
| 816 |
+
#[derive(Debug, Clone)]
|
| 817 |
+
pub struct RelatorioJepaAdaptativa {
|
| 818 |
+
pub base: RelatorioJepa,
|
| 819 |
+
pub fracs: Vec<f64>,
|
| 820 |
+
/// (época, braço, frac, perda_pred) por época — trilha auditável.
|
| 821 |
+
pub escolhas: Vec<(usize, usize, f64, f64)>,
|
| 822 |
+
/// nº de épocas por braço.
|
| 823 |
+
pub jogadas_por_braco: Vec<usize>,
|
| 824 |
+
/// médias posteriores finais (estimativa de qualidade relativa).
|
| 825 |
+
pub posterior: Vec<f64>,
|
| 826 |
+
}
|
| 827 |
+
|
| 828 |
+
/// **T-J1 (conexão Information Bottleneck — o porquê de a fração importar):**
|
| 829 |
+
/// o JEPA minimiza `E‖s(y) − f(s(x_ctx))‖²` com alvo mascarado. Sob ruído
|
| 830 |
+
/// gaussiano homocedástico, minimizar o MSE equivale a maximizar um limite
|
| 831 |
+
/// inferior de `I(Z; Y | X_ctx)` (o preditor ótimo é `E[s(Y)|X_ctx]` e o
|
| 832 |
+
/// resíduo é o ruído epistêmico — princípio InfoMax). A fração `f` controla o
|
| 833 |
+
/// TRADE-OFF: f→0 torna o alvo visível no contexto (atalho de identidade,
|
| 834 |
+
/// informação trivial); f→1 destrói o contexto (erro de estimação domina).
|
| 835 |
+
/// A perda preditiva em função de f é, portanto, em U — com ótimo dependente
|
| 836 |
+
/// dos dados. NENHUM f fixo é ótimo universalmente (por isso a adaptação).
|
| 837 |
+
///
|
| 838 |
+
/// **T-J2 (esquema e recompensa honesta):** um bandit Thompson (T-TS1) sobre
|
| 839 |
+
/// `f ∈ fracs` escolhe a fração de cada época. A recompensa usa a PERDA
|
| 840 |
+
/// PREDITIVA ISOLADA `l_p` (critério honesto herdado da v0.5.0 — a VC pode
|
| 841 |
+
/// crescer legitimamente) e mede MELHORIA RELATIVA do próprio braço:
|
| 842 |
+
/// `r = ½ + (l_prev − l)/(2·l_prev)` ∈ [0,1] — ½ = estagnação, >½ =
|
| 843 |
+
/// melhorando. Escalar por l_prev torna r comparável entre regimes de perda
|
| 844 |
+
/// diferentes; oscilações simétricas não enganam o posterior em expectativa
|
| 845 |
+
/// (r médio = ½). O RNG do bandit é um FLUXO SEPARADO (semente derivada) —
|
| 846 |
+
/// o fluxo de máscara (cfg.semente) permanece intocado: com um único braço,
|
| 847 |
+
/// o treino é BIT-A-BIT idêntico ao `treinar_variante` com fração fixa
|
| 848 |
+
/// (teste `jepa_adaptativa_um_braco_e_fixa`).
|
| 849 |
+
pub fn treinar_variante_adaptativa(
|
| 850 |
+
patches: &Tensor,
|
| 851 |
+
grid: &[usize],
|
| 852 |
+
fracs: &[f64],
|
| 853 |
+
cfg: &CfgJepa,
|
| 854 |
+
variante: VarianteJepa,
|
| 855 |
+
cond: Option<&Tensor>,
|
| 856 |
+
dev: &Device,
|
| 857 |
+
) -> Result<RelatorioJepaAdaptativa> {
|
| 858 |
+
if fracs.is_empty() {
|
| 859 |
+
candle_core::bail!("treinar_variante_adaptativa: fracs não pode ser vazio");
|
| 860 |
+
}
|
| 861 |
+
let fracs: Vec<f64> = fracs.iter().cloned().map(|f| f.clamp(0.05, 0.95)).collect();
|
| 862 |
+
// fluxo de RNG SEPARADO do bandit — preserva o fluxo de máscara
|
| 863 |
+
let mut bandit = crate::controlador::ThompsonSampling::novo(
|
| 864 |
+
fracs.iter().map(|f| format!("frac={f:.2}")).collect(),
|
| 865 |
+
cfg.semente ^ 0xBADC0DEu64,
|
| 866 |
+
);
|
| 867 |
+
let mut ultima_perda_braco = vec![f64::NAN; fracs.len()];
|
| 868 |
+
let mut escolhas: Vec<(usize, usize, f64, f64)> = Vec::with_capacity(cfg.epocas);
|
| 869 |
+
|
| 870 |
+
// ---- corpo: réplica fiel do loop de `treinar_variante` (mesma ordem de
|
| 871 |
+
// ops; única diferença: a fração vem do bandit por época) ----
|
| 872 |
+
let n = patches.dim(0)?;
|
| 873 |
+
let d_patch = patches.dim(1)?;
|
| 874 |
+
let mut rng = RngDeterministico::novo(cfg.semente);
|
| 875 |
+
let mut nucleo = NucleoJepa::novo_com_semente(
|
| 876 |
+
d_patch,
|
| 877 |
+
cfg.d_emb,
|
| 878 |
+
if cond.is_some() { cond.unwrap().dim(1)? } else { 0 },
|
| 879 |
+
n,
|
| 880 |
+
cfg.m,
|
| 881 |
+
cfg.lambda_vc,
|
| 882 |
+
cfg.gamma_vc,
|
| 883 |
+
cfg.semente,
|
| 884 |
+
dev,
|
| 885 |
+
)?;
|
| 886 |
+
let otimo_vars = nucleo.vars();
|
| 887 |
+
let mut otimizador = crate::treino::AdamWManual::novo(otimo_vars, cfg.lr, 0.01);
|
| 888 |
+
let z0 = nucleo.codificar(patches)?;
|
| 889 |
+
let std0 = std_por_dim(&z0)?;
|
| 890 |
+
let mut perda_inicial = f64::NAN;
|
| 891 |
+
let mut perda_final = f64::NAN;
|
| 892 |
+
let mut pred_inicial = f64::NAN;
|
| 893 |
+
let mut pred_final = f64::NAN;
|
| 894 |
+
let mut vc_final = f64::NAN;
|
| 895 |
+
|
| 896 |
+
for epoca in 0..cfg.epocas {
|
| 897 |
+
// === escolha do braço (fração) desta época ===
|
| 898 |
+
let k = bandit.escolher();
|
| 899 |
+
let frac_braco = fracs[k];
|
| 900 |
+
let (ctx_idx, blocos) = loop {
|
| 901 |
+
let mut masc = mascara_por_variante(variante, grid, frac_braco, &mut rng);
|
| 902 |
+
if masc.len() < n {
|
| 903 |
+
masc.resize(n, false);
|
| 904 |
+
}
|
| 905 |
+
masc.truncate(n);
|
| 906 |
+
let ctx: Vec<u32> = (0..n).filter(|&i| !masc[i]).map(|i| i as u32).collect();
|
| 907 |
+
let alvos: Vec<usize> = (0..n).filter(|&i| masc[i]).collect();
|
| 908 |
+
if !ctx.is_empty() && !alvos.is_empty() {
|
| 909 |
+
break (ctx, alvos);
|
| 910 |
+
}
|
| 911 |
+
};
|
| 912 |
+
let grupos: Vec<Vec<usize>> = blocos
|
| 913 |
+
.chunks(cfg.tam_bloco.max(1))
|
| 914 |
+
.map(|c| c.to_vec())
|
| 915 |
+
.collect();
|
| 916 |
+
if ctx_idx.is_empty() || grupos.is_empty() {
|
| 917 |
+
continue;
|
| 918 |
+
}
|
| 919 |
+
let dev2 = patches.device();
|
| 920 |
+
let n_ctx = ctx_idx.len();
|
| 921 |
+
let idx_t = Tensor::from_vec(ctx_idx, (n_ctx,), &dev2)?;
|
| 922 |
+
let ctx_patches = patches.index_select(&idx_t, 0)?;
|
| 923 |
+
let z_ctx_todos = nucleo.codificar(&ctx_patches)?;
|
| 924 |
+
let z_ctx = z_ctx_todos.mean_keepdim(0)?;
|
| 925 |
+
let mut alvos: Vec<Vec<(usize, Tensor)>> = Vec::new();
|
| 926 |
+
for g in &grupos {
|
| 927 |
+
let idx: Vec<u32> = g.iter().map(|&i| i as u32).collect();
|
| 928 |
+
let n_g = idx.len();
|
| 929 |
+
let it = Tensor::from_vec(idx, (n_g,), &dev2)?;
|
| 930 |
+
alvos.push(vec![(g[0], patches.index_select(&it, 0)?)]);
|
| 931 |
+
}
|
| 932 |
+
let (perda, lp, lv) = nucleo.perda_lote(&z_ctx, &[alvos_concat(&alvos)], cond, &z_ctx_todos)?;
|
| 933 |
+
let p = perda.to_scalar::<f32>()? as f64;
|
| 934 |
+
if p.is_finite() {
|
| 935 |
+
if epoca == 0 {
|
| 936 |
+
perda_inicial = p;
|
| 937 |
+
pred_inicial = lp;
|
| 938 |
+
}
|
| 939 |
+
perda_final = p;
|
| 940 |
+
pred_final = lp;
|
| 941 |
+
vc_final = lv;
|
| 942 |
+
}
|
| 943 |
+
// === recompensa do braço (T-J2): melhoria relativa da perda PREDITIVA ===
|
| 944 |
+
if lp.is_finite() {
|
| 945 |
+
let anterior = ultima_perda_braco[k];
|
| 946 |
+
let r = if anterior.is_finite() && anterior > 0.0 {
|
| 947 |
+
(0.5 + (anterior - lp) / (2.0 * anterior)).clamp(0.0, 1.0)
|
| 948 |
+
} else {
|
| 949 |
+
0.5 // primeira medida do braço: neutra
|
| 950 |
+
};
|
| 951 |
+
bandit.registrar_continuo(k, r);
|
| 952 |
+
ultima_perda_braco[k] = lp;
|
| 953 |
+
escolhas.push((epoca, k, frac_braco, lp));
|
| 954 |
+
}
|
| 955 |
+
// decaimento cosseno do lr + clipping (idêntico ao fixo)
|
| 956 |
+
let frac = epoca as f64 / cfg.epocas.max(1) as f64;
|
| 957 |
+
let lr_t = cfg.lr * 0.5 * (1.0 + (std::f64::consts::PI * frac).cos());
|
| 958 |
+
otimizador.set_lr(lr_t);
|
| 959 |
+
let mut grads = crate::treino::grads_achatados(&perda, &nucleo.vars())?;
|
| 960 |
+
crate::treino::AdamWManual::clipar(&mut grads, 1.0);
|
| 961 |
+
otimizador.passo(grads);
|
| 962 |
+
nucleo.ema_passo()?;
|
| 963 |
+
}
|
| 964 |
+
let z1 = nucleo.codificar(patches)?;
|
| 965 |
+
let std1 = std_por_dim(&z1)?;
|
| 966 |
+
let _ = (pred_inicial, vc_final);
|
| 967 |
+
let jogadas_por_braco = bandit.jogadas.iter().map(|&c| c as usize).collect();
|
| 968 |
+
let posterior = bandit.medias_posteriori();
|
| 969 |
+
Ok(RelatorioJepaAdaptativa {
|
| 970 |
+
base: RelatorioJepa {
|
| 971 |
+
variante: variante.nome().to_string(),
|
| 972 |
+
perda_inicial,
|
| 973 |
+
perda_final,
|
| 974 |
+
perda_pred_inicial: pred_inicial,
|
| 975 |
+
perda_pred_final: pred_final,
|
| 976 |
+
std_emb_final: std1,
|
| 977 |
+
std_emb_inicial: std0,
|
| 978 |
+
n_patches: n,
|
| 979 |
+
d_patch,
|
| 980 |
+
epocas: cfg.epocas,
|
| 981 |
+
},
|
| 982 |
+
fracs,
|
| 983 |
+
escolhas,
|
| 984 |
+
jogadas_por_braco,
|
| 985 |
+
posterior,
|
| 986 |
+
})
|
| 987 |
+
}
|
| 988 |
+
|
| 989 |
+
fn std_por_dim(z: &Tensor) -> Result<f64> {
|
| 990 |
+
let n = z.dim(0)? as f64;
|
| 991 |
+
let media = z.mean_keepdim(0)?;
|
| 992 |
+
let centr = z.broadcast_sub(&media)?;
|
| 993 |
+
let var = (centr.sqr()?.sum(0)? / n)?;
|
| 994 |
+
Ok(var.sqrt()?.mean_all()?.to_scalar::<f32>()? as f64)
|
| 995 |
+
}
|
| 996 |
+
|
| 997 |
+
// ---------------------------------------------------------------------------
|
| 998 |
+
// Parte 5 — SonicMoE (IO/tile-aware)
|
| 999 |
+
// ---------------------------------------------------------------------------
|
| 1000 |
+
|
| 1001 |
+
/// MoE top-k com capacidade por especialista e perda de balanceamento.
|
| 1002 |
+
///
|
| 1003 |
+
/// **Prova SM-1 (limite de capacidade):** o roteamento tile-aware mantém no
|
| 1004 |
+
/// máximo `C` tokens por especialista por ladrilho — o drop é determinístico
|
| 1005 |
+
/// (ordem de chegada) e o nº de tokens descartados é `Σ_e max(0, load_e − C)`.
|
| 1006 |
+
///
|
| 1007 |
+
/// **Prova SM-2 (balanceamento):** `L_aux = E·Σ_e f_e·p_e`; com roteamento
|
| 1008 |
+
/// auto-consistente (`f = p`), `L_aux = E·Σ p_e² ≥ 1` com igualdade sse
|
| 1009 |
+
/// `p_e = 1/E` (Cauchy–Schchwarz) — o mínimo força o roteador uniforme.
|
| 1010 |
+
pub struct SonicMoe {
|
| 1011 |
+
pub roteador: Linear, // d → E
|
| 1012 |
+
pub especialistas: Vec<MlpJepa>, // E experts d → d
|
| 1013 |
+
pub n_top: usize,
|
| 1014 |
+
pub capacidade: usize,
|
| 1015 |
+
pub lambda_aux: f64,
|
| 1016 |
+
}
|
| 1017 |
+
|
| 1018 |
+
pub struct SaidaMoe {
|
| 1019 |
+
pub saida: Tensor,
|
| 1020 |
+
pub perda_aux: Tensor,
|
| 1021 |
+
pub carga: Vec<usize>, // tokens efetivamente processados por especialista
|
| 1022 |
+
pub descartados: usize, // tokens com especialistas cortados pela capacidade
|
| 1023 |
+
}
|
| 1024 |
+
|
| 1025 |
+
impl SonicMoe {
|
| 1026 |
+
pub fn novo(d: usize, n_expertos: usize, n_top: usize, capacidade: usize, dev: &Device) -> Result<Self> {
|
| 1027 |
+
let roteador = Linear::sem_vies(d, n_expertos, 0.1, dev)?;
|
| 1028 |
+
let especialistas = (0..n_expertos)
|
| 1029 |
+
.map(|_| MlpJepa::novo(d, 2 * d, d, dev))
|
| 1030 |
+
.collect::<Result<Vec<_>>>()?;
|
| 1031 |
+
Ok(Self { roteador, especialistas, n_top, capacidade, lambda_aux: 0.01 })
|
| 1032 |
+
}
|
| 1033 |
+
|
| 1034 |
+
pub fn vars(&self) -> Vec<Var> {
|
| 1035 |
+
let mut v = self.roteador.vars();
|
| 1036 |
+
for e in &self.especialistas {
|
| 1037 |
+
v.extend(e.vars());
|
| 1038 |
+
}
|
| 1039 |
+
v
|
| 1040 |
+
}
|
| 1041 |
+
|
| 1042 |
+
/// Forward tile-aware: (n, d) → (n, d).
|
| 1043 |
+
pub fn fwd(&self, x: &Tensor) -> Result<SaidaMoe> {
|
| 1044 |
+
let n = x.dim(0)?;
|
| 1045 |
+
let d = x.dim(1)?;
|
| 1046 |
+
let e = self.especialistas.len();
|
| 1047 |
+
let dev = x.device();
|
| 1048 |
+
let logits = self.roteador.fwd(x)?; // (n, E)
|
| 1049 |
+
let probs = candle_nn::ops::softmax(&logits, candle_core::D::Minus1)?;
|
| 1050 |
+
|
| 1051 |
+
// top-k por token (não diferenciável — pesos do top-k são dif. via probs)
|
| 1052 |
+
let probs_v = probs.to_vec2::<f32>()?;
|
| 1053 |
+
let mut carga = vec![0usize; e];
|
| 1054 |
+
let mut descartados = 0usize;
|
| 1055 |
+
// acumulador de saída (n, d) — construído por soma de contribuições
|
| 1056 |
+
let mut saida = Tensor::zeros((n, d), candle_core::DType::F32, &dev)?;
|
| 1057 |
+
let mut peso_total = Tensor::zeros((n, 1), candle_core::DType::F32, &dev)?;
|
| 1058 |
+
// por especialista (tile): tokens cujo top-k contém e e com carga < C
|
| 1059 |
+
for ei in 0..e {
|
| 1060 |
+
// candidatos: tokens ordenados por prob do especialista ei
|
| 1061 |
+
let mut cand: Vec<(usize, f32)> = (0..n)
|
| 1062 |
+
.map(|i| (i, probs_v[i][ei]))
|
| 1063 |
+
.collect();
|
| 1064 |
+
cand.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
| 1065 |
+
let mut escolhidos: Vec<usize> = Vec::new();
|
| 1066 |
+
for (i, _) in cand {
|
| 1067 |
+
if carga[ei] >= self.capacidade {
|
| 1068 |
+
break;
|
| 1069 |
+
}
|
| 1070 |
+
// verifica se ei está no top-k do token i
|
| 1071 |
+
let mut ordem: Vec<(f32, usize)> =
|
| 1072 |
+
(0..e).map(|k| (probs_v[i][k], k)).collect();
|
| 1073 |
+
ordem.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap());
|
| 1074 |
+
let top: Vec<usize> = ordem.iter().rev().take(self.n_top).map(|(_, k)| *k).collect();
|
| 1075 |
+
if top.contains(&ei) {
|
| 1076 |
+
escolhidos.push(i);
|
| 1077 |
+
carga[ei] += 1;
|
| 1078 |
+
}
|
| 1079 |
+
}
|
| 1080 |
+
if escolhidos.is_empty() {
|
| 1081 |
+
continue;
|
| 1082 |
+
}
|
| 1083 |
+
// tile do especialista: uma única passagem em lote
|
| 1084 |
+
let idx: Vec<u32> = escolhidos.iter().map(|&i| i as u32).collect();
|
| 1085 |
+
let n_e = idx.len();
|
| 1086 |
+
let idx_t = Tensor::from_vec(idx, (n_e,), &dev)?;
|
| 1087 |
+
let tile = x.index_select(&idx_t, 0)?;
|
| 1088 |
+
let y = self.especialistas[ei].fwd(&tile)?; // (m, d)
|
| 1089 |
+
// pesos: prob do especialista ei para cada token escolhido
|
| 1090 |
+
let w: Vec<f32> = escolhidos.iter().map(|&i| probs_v[i][ei]).collect();
|
| 1091 |
+
let n_w = escolhidos.len();
|
| 1092 |
+
let w_t = Tensor::from_vec(w.clone(), (n_w, 1), &dev)?;
|
| 1093 |
+
let contrib = y.broadcast_mul(&w_t)?;
|
| 1094 |
+
// scatter-add: acumula nas posições
|
| 1095 |
+
let mut buf = saida.to_vec2::<f32>()?;
|
| 1096 |
+
let contrib_v = contrib.to_vec2::<f32>()?;
|
| 1097 |
+
for (k, &i) in escolhidos.iter().enumerate() {
|
| 1098 |
+
for j in 0..d {
|
| 1099 |
+
buf[i][j] += contrib_v[k][j];
|
| 1100 |
+
}
|
| 1101 |
+
}
|
| 1102 |
+
saida = Tensor::from_vec(
|
| 1103 |
+
buf.into_iter().flatten().collect::<Vec<f32>>(),
|
| 1104 |
+
(n, d),
|
| 1105 |
+
&dev,
|
| 1106 |
+
)?;
|
| 1107 |
+
let mut pt = peso_total.to_vec2::<f32>()?;
|
| 1108 |
+
for (k, &i) in escolhidos.iter().enumerate() {
|
| 1109 |
+
pt[i][0] += w[k];
|
| 1110 |
+
}
|
| 1111 |
+
peso_total = Tensor::from_vec(
|
| 1112 |
+
pt.into_iter().flatten().collect::<Vec<f32>>(),
|
| 1113 |
+
(n, 1),
|
| 1114 |
+
&dev,
|
| 1115 |
+
)?;
|
| 1116 |
+
}
|
| 1117 |
+
// tokens sem especialista (todos cortados) → descartados; saída 0
|
| 1118 |
+
let pt_v = peso_total.to_vec2::<f32>()?;
|
| 1119 |
+
for i in 0..n {
|
| 1120 |
+
if pt_v[i][0] == 0.0 {
|
| 1121 |
+
descartados += 1;
|
| 1122 |
+
}
|
| 1123 |
+
}
|
| 1124 |
+
// renormaliza pelo peso efetivo (evita viés da capacidade)
|
| 1125 |
+
let denom = peso_total.clamp(1e-6, f64::INFINITY)?;
|
| 1126 |
+
let saida = saida.broadcast_div(&denom)?;
|
| 1127 |
+
|
| 1128 |
+
// perda auxiliar: E·Σ f_e·p_e (f = fração de tokens cujo top-1 é e)
|
| 1129 |
+
let mut f = vec![0f32; e];
|
| 1130 |
+
for i in 0..n {
|
| 1131 |
+
let (mut melhor, mut mp) = (0usize, f32::NEG_INFINITY);
|
| 1132 |
+
for k in 0..e {
|
| 1133 |
+
if probs_v[i][k] > mp {
|
| 1134 |
+
mp = probs_v[i][k];
|
| 1135 |
+
melhor = k;
|
| 1136 |
+
}
|
| 1137 |
+
}
|
| 1138 |
+
f[melhor] += 1.0 / n as f32;
|
| 1139 |
+
}
|
| 1140 |
+
let f_t = Tensor::from_vec(f, (e,), &dev)?;
|
| 1141 |
+
let p_media = probs.mean(0)?; // (E,)
|
| 1142 |
+
let perda_aux = ((&f_t * &p_media)?.sum_all()? * (e as f64))?;
|
| 1143 |
+
Ok(SaidaMoe { saida, perda_aux, carga, descartados })
|
| 1144 |
+
}
|
| 1145 |
+
}
|
| 1146 |
+
|
| 1147 |
+
// ---------------------------------------------------------------------------
|
| 1148 |
+
// Parte 6 — ReplaySSM
|
| 1149 |
+
// ---------------------------------------------------------------------------
|
| 1150 |
+
|
| 1151 |
+
/// SSM de porta diagonal com replay para aprendizado continuado.
|
| 1152 |
+
///
|
| 1153 |
+
/// Recorrência: `h_t = σ(a) ⊙ h_{t−1} + (1 − σ(a)) ⊙ (B x_t)`.
|
| 1154 |
+
///
|
| 1155 |
+
/// **Prova R-1 (estabilidade):** `|σ(a)| < 1` ⇒ perturbações de `h_0`
|
| 1156 |
+
/// decaem geometricamente (mesma estrutura do Teo S-2' do SSD-Δ).
|
| 1157 |
+
///
|
| 1158 |
+
/// **Prova R-2 (limite de esquecimento):** o passo de treino mistura
|
| 1159 |
+
/// gradientes `(1−α)·∇L_novo + α·∇L_replay`; para `L_antiga` μ-fortemente
|
| 1160 |
+
/// convexa com gradientes novos limitados por `G`, o afastamento do ótimo
|
| 1161 |
+
/// antigo por passo é `≤ (1−α)·η·G` (sem replay: `≤ η·G`) — o replay
|
| 1162 |
+
/// contrai o drift por um fator `1/(1−α)` no regime linear.
|
| 1163 |
+
pub struct ReplaySsm {
|
| 1164 |
+
pub a: Var, // (d,) — porta (sigmoid no uso)
|
| 1165 |
+
pub b: Var, // (d_in, d) — projeção de entrada
|
| 1166 |
+
pub d: usize,
|
| 1167 |
+
pub alpha_replay: f64,
|
| 1168 |
+
pub buffer: Vec<Tensor>,
|
| 1169 |
+
pub capacidade_buffer: usize,
|
| 1170 |
+
}
|
| 1171 |
+
|
| 1172 |
+
impl ReplaySsm {
|
| 1173 |
+
pub fn novo(d_in: usize, d: usize, alpha_replay: f64, capacidade_buffer: usize, dev: &Device) -> Result<Self> {
|
| 1174 |
+
let a_t = Tensor::randn(0f32, 0.5, (d,), dev)?;
|
| 1175 |
+
let b_t = Tensor::randn(0f32, 0.1, (d_in, d), dev)?;
|
| 1176 |
+
Ok(Self {
|
| 1177 |
+
a: Var::from_tensor(&a_t)?,
|
| 1178 |
+
b: Var::from_tensor(&b_t)?,
|
| 1179 |
+
d,
|
| 1180 |
+
alpha_replay,
|
| 1181 |
+
buffer: Vec::new(),
|
| 1182 |
+
capacidade_buffer,
|
| 1183 |
+
})
|
| 1184 |
+
}
|
| 1185 |
+
|
| 1186 |
+
pub fn vars(&self) -> Vec<Var> {
|
| 1187 |
+
vec![self.a.clone(), self.b.clone()]
|
| 1188 |
+
}
|
| 1189 |
+
|
| 1190 |
+
/// Scan: x (T, d_in) → h (T, d). Diferenciável (candle autograd).
|
| 1191 |
+
pub fn scan(&self, x: &Tensor) -> Result<Tensor> {
|
| 1192 |
+
let t = x.dim(0)?;
|
| 1193 |
+
let dev = x.device();
|
| 1194 |
+
let porta = candle_nn::ops::sigmoid(&self.a.as_tensor().broadcast_as((1, self.d))?)?; // (1, d)
|
| 1195 |
+
let um_menos = porta.affine(-1.0, 1.0)?; // 1 − σ(a)
|
| 1196 |
+
let entrada = x.matmul(&self.b.as_tensor())?; // (T, d)
|
| 1197 |
+
let mut h = Tensor::zeros((1, self.d), candle_core::DType::F32, &dev)?;
|
| 1198 |
+
let mut saidas: Vec<Tensor> = Vec::with_capacity(t);
|
| 1199 |
+
for i in 0..t {
|
| 1200 |
+
let xt = entrada.narrow(0, i, 1)?;
|
| 1201 |
+
h = ((&porta * &h)? + (&um_menos * &xt)?)?;
|
| 1202 |
+
saidas.push(h.clone());
|
| 1203 |
+
}
|
| 1204 |
+
Ok(Tensor::stack(&saidas, 0)?)
|
| 1205 |
+
}
|
| 1206 |
+
|
| 1207 |
+
/// Empurra uma janela de treino no buffer (FIFO).
|
| 1208 |
+
pub fn guardar(&mut self, x: &Tensor) {
|
| 1209 |
+
if self.buffer.len() >= self.capacidade_buffer {
|
| 1210 |
+
self.buffer.remove(0);
|
| 1211 |
+
}
|
| 1212 |
+
self.buffer.push(x.detach());
|
| 1213 |
+
}
|
| 1214 |
+
|
| 1215 |
+
/// Um passo de treino no lote novo (com mistura de replay).
|
| 1216 |
+
/// Devolve (perda_total, perda_novo, perda_replay).
|
| 1217 |
+
pub fn passo_treino(
|
| 1218 |
+
&mut self,
|
| 1219 |
+
x: &Tensor,
|
| 1220 |
+
alvo: &Tensor,
|
| 1221 |
+
otimizador: &mut crate::treino::AdamWManual,
|
| 1222 |
+
) -> Result<(f64, f64, f64)> {
|
| 1223 |
+
// perda nova
|
| 1224 |
+
let h = self.scan(x)?;
|
| 1225 |
+
let dif = (&h - alvo)?;
|
| 1226 |
+
let perda_novo = dif.sqr()?.mean_all()?;
|
| 1227 |
+
// replay
|
| 1228 |
+
let mut perda_total = perda_novo.clone();
|
| 1229 |
+
let mut p_r = 0.0;
|
| 1230 |
+
if !self.buffer.is_empty() {
|
| 1231 |
+
let idx = self.buffer.len() - 1;
|
| 1232 |
+
let antigo = self.buffer[idx].clone();
|
| 1233 |
+
let hr = self.scan(&antigo)?;
|
| 1234 |
+
// alvo do replay: manter a dinâmica aprendida estável (magnitudes
|
| 1235 |
+
// finitas) — auto-supervisão de estabilidade
|
| 1236 |
+
let dr = hr.sqr()?.mean_all()?;
|
| 1237 |
+
perda_total = (perda_total.affine(1.0 - self.alpha_replay, 0.0)?
|
| 1238 |
+
+ dr.affine(self.alpha_replay, 0.0)?)?;
|
| 1239 |
+
p_r = dr.to_scalar::<f32>()? as f64;
|
| 1240 |
+
}
|
| 1241 |
+
let pn = perda_novo.to_scalar::<f32>()? as f64;
|
| 1242 |
+
otimizador.passo(crate::treino::grads_achatados(&perda_total, &self.vars())?);
|
| 1243 |
+
self.guardar(x);
|
| 1244 |
+
let pt = perda_total.to_scalar::<f32>()? as f64;
|
| 1245 |
+
Ok((pt, pn, p_r))
|
| 1246 |
+
}
|
| 1247 |
+
}
|
| 1248 |
+
|
| 1249 |
+
// ---------------------------------------------------------------------------
|
| 1250 |
+
// Variantes especiais: LeJEPA (edição-condicionado), MC-JEPA (cruz-modal),
|
| 1251 |
+
// BJEPA (bidirecional com consistência de ciclo)
|
| 1252 |
+
// ---------------------------------------------------------------------------
|
| 1253 |
+
|
| 1254 |
+
/// **LeJEPA** — predição latente condicionada por EDIÇÃO: contexto = E(x),
|
| 1255 |
+
/// condição = instrução de edição `e` (vetor de baixa dimensão), alvo =
|
| 1256 |
+
/// `E_ξ(edit_e(x))`. A condição é a INSTRUÇÃO (não o resultado — sem
|
| 1257 |
+
/// vazamento: `e` não contém `edit(x)`).
|
| 1258 |
+
pub fn treinar_lejepa(
|
| 1259 |
+
dados: &Tensor, // (n, d_patch) — originais
|
| 1260 |
+
edits: &Tensor, // (n, d_cond) — instruções de edição
|
| 1261 |
+
editados: &Tensor, // (n, d_patch) — resultados das edições
|
| 1262 |
+
cfg: &CfgJepa,
|
| 1263 |
+
dev: &Device,
|
| 1264 |
+
) -> Result<RelatorioJepa> {
|
| 1265 |
+
let n = dados.dim(0)?;
|
| 1266 |
+
let d_patch = dados.dim(1)?;
|
| 1267 |
+
let d_cond = edits.dim(1)?;
|
| 1268 |
+
let mut rng = RngDeterministico::novo(cfg.semente);
|
| 1269 |
+
let mut nucleo = NucleoJepa::novo_com_semente(
|
| 1270 |
+
d_patch, cfg.d_emb, d_cond, 1, cfg.m, cfg.lambda_vc, cfg.gamma_vc, cfg.semente, dev,
|
| 1271 |
+
)?;
|
| 1272 |
+
let mut otim = crate::treino::AdamWManual::novo(nucleo.vars(), cfg.lr, 0.01);
|
| 1273 |
+
let mut perda_inicial = f64::NAN;
|
| 1274 |
+
let mut perda_final = f64::NAN;
|
| 1275 |
+
let z0 = nucleo.codificar(dados)?;
|
| 1276 |
+
let std0 = std_por_dim(&z0)?;
|
| 1277 |
+
for epoca in 0..cfg.epocas {
|
| 1278 |
+
// subamostra do lote
|
| 1279 |
+
let idx = rng.amostra_k_de_n(n.min(64), n);
|
| 1280 |
+
let sel: Vec<u32> = idx.iter().map(|&i| i as u32).collect();
|
| 1281 |
+
let it = Tensor::from_vec(sel.clone(), (sel.len(),), dev)?;
|
| 1282 |
+
let xb = dados.index_select(&it, 0)?;
|
| 1283 |
+
let eb = edits.index_select(&it, 0)?;
|
| 1284 |
+
let yb = editados.index_select(&it, 0)?;
|
| 1285 |
+
let z_ctx = nucleo.codificar(&xb)?; // (m, d_emb)
|
| 1286 |
+
let mut perdas: Vec<Tensor> = Vec::new();
|
| 1287 |
+
for i in 0..xb.dim(0)? {
|
| 1288 |
+
let zc = z_ctx.get(i)?.broadcast_as((1, cfg.d_emb))?;
|
| 1289 |
+
let e_i = eb.get(i)?.broadcast_as((1, d_cond))?;
|
| 1290 |
+
let yt = yb.get(i)?.broadcast_as((1, d_patch))?;
|
| 1291 |
+
let zt = nucleo
|
| 1292 |
+
.alvo_codificar(&yt)?
|
| 1293 |
+
.mean(0)?
|
| 1294 |
+
.broadcast_as((1, cfg.d_emb))?;
|
| 1295 |
+
let pred = nucleo.predizer(&zc, 0, Some(&e_i))?;
|
| 1296 |
+
let dif = (&pred - &zt)?;
|
| 1297 |
+
perdas.push((dif.sqr()?.sum_all()? / cfg.d_emb as f64)?);
|
| 1298 |
+
}
|
| 1299 |
+
let l_pred = (Tensor::stack(&perdas, 0)?.sum_all()? / perdas.len() as f64)?;
|
| 1300 |
+
let l_vc = nucleo.perda_vc(&z_ctx)?;
|
| 1301 |
+
let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(cfg.lambda_vc, 0.0)?)?;
|
| 1302 |
+
let p = total.to_scalar::<f32>()? as f64;
|
| 1303 |
+
if p.is_finite() {
|
| 1304 |
+
if epoca == 0 {
|
| 1305 |
+
perda_inicial = p;
|
| 1306 |
+
}
|
| 1307 |
+
perda_final = p;
|
| 1308 |
+
}
|
| 1309 |
+
let mut grads = crate::treino::grads_achatados(&total, &nucleo.vars())?;
|
| 1310 |
+
crate::treino::AdamWManual::clipar(&mut grads, 1.0);
|
| 1311 |
+
otim.passo(grads);
|
| 1312 |
+
nucleo.ema_passo()?;
|
| 1313 |
+
}
|
| 1314 |
+
let z1 = nucleo.codificar(dados)?;
|
| 1315 |
+
Ok(RelatorioJepa {
|
| 1316 |
+
variante: "LeJEPA".into(),
|
| 1317 |
+
perda_inicial,
|
| 1318 |
+
perda_final,
|
| 1319 |
+
perda_pred_inicial: perda_inicial,
|
| 1320 |
+
perda_pred_final: perda_final,
|
| 1321 |
+
std_emb_final: std_por_dim(&z1)?,
|
| 1322 |
+
std_emb_inicial: std0,
|
| 1323 |
+
n_patches: n,
|
| 1324 |
+
d_patch,
|
| 1325 |
+
epocas: cfg.epocas,
|
| 1326 |
+
})
|
| 1327 |
+
}
|
| 1328 |
+
|
| 1329 |
+
/// **MC-JEPA** — cruz-modal: dois codificadores, predição nos DOIS sentidos
|
| 1330 |
+
/// (A→B e B→A) com preditores separados e alvos EMA dos dois lados.
|
| 1331 |
+
pub fn treinar_mcjepa(
|
| 1332 |
+
mod_a: &Tensor, // (n, d_a)
|
| 1333 |
+
mod_b: &Tensor, // (n, d_b)
|
| 1334 |
+
cfg: &CfgJepa,
|
| 1335 |
+
dev: &Device,
|
| 1336 |
+
) -> Result<RelatorioJepa> {
|
| 1337 |
+
let n = mod_a.dim(0)?;
|
| 1338 |
+
let d_a = mod_a.dim(1)?;
|
| 1339 |
+
let d_b = mod_b.dim(1)?;
|
| 1340 |
+
// núcleo A→B e núcleo B→A (compartilham apenas a estrutura)
|
| 1341 |
+
let mut nb = NucleoJepa::novo_com_semente(d_a, cfg.d_emb, 0, 1, cfg.m, cfg.lambda_vc, cfg.gamma_vc, cfg.semente, dev)?;
|
| 1342 |
+
let mut na = NucleoJepa::novo_com_semente(d_b, cfg.d_emb, 0, 1, cfg.m, cfg.lambda_vc, cfg.gamma_vc, cfg.semente, dev)?;
|
| 1343 |
+
let mut otim = crate::treino::AdamWManual::novo(
|
| 1344 |
+
[nb.vars(), na.vars()].concat(),
|
| 1345 |
+
cfg.lr,
|
| 1346 |
+
0.01,
|
| 1347 |
+
);
|
| 1348 |
+
let mut rng = RngDeterministico::novo(cfg.semente);
|
| 1349 |
+
let mut perda_inicial = f64::NAN;
|
| 1350 |
+
let mut perda_final = f64::NAN;
|
| 1351 |
+
for epoca in 0..cfg.epocas {
|
| 1352 |
+
let idx = rng.amostra_k_de_n(n.min(64), n);
|
| 1353 |
+
let sel: Vec<u32> = idx.iter().map(|&i| i as u32).collect();
|
| 1354 |
+
let it = Tensor::from_vec(sel, (idx.len(),), dev)?;
|
| 1355 |
+
let xa = mod_a.index_select(&it, 0)?;
|
| 1356 |
+
let xb = mod_b.index_select(&it, 0)?;
|
| 1357 |
+
// z_a = E_a(x_a) prevê ẑ_b; z_b = E_b(x_b) prevê ẑ_a
|
| 1358 |
+
let z_a = nb.codificar(&xa)?; // (m, d_emb)
|
| 1359 |
+
let z_b = na.codificar(&xb)?;
|
| 1360 |
+
let t_a = na.alvo_codificar(&xa)?; // alvo EMA do lado A
|
| 1361 |
+
let t_b = nb.alvo_codificar(&xb)?; // alvo EMA do lado B
|
| 1362 |
+
let m_ = xa.dim(0)?;
|
| 1363 |
+
let mut perdas: Vec<Tensor> = Vec::new();
|
| 1364 |
+
for i in 0..m_ {
|
| 1365 |
+
// A → B
|
| 1366 |
+
let zc = z_a.get(i)?.broadcast_as((1, cfg.d_emb))?;
|
| 1367 |
+
let zt = t_b.get(i)?.broadcast_as((1, cfg.d_emb))?;
|
| 1368 |
+
let pred = nb.predizer(&zc, 0, None)?;
|
| 1369 |
+
let dif = (&pred - &zt)?;
|
| 1370 |
+
perdas.push((dif.sqr()?.sum_all()? / cfg.d_emb as f64)?);
|
| 1371 |
+
// B → A
|
| 1372 |
+
let zc2 = z_b.get(i)?.broadcast_as((1, cfg.d_emb))?;
|
| 1373 |
+
let zt2 = t_a.get(i)?.broadcast_as((1, cfg.d_emb))?;
|
| 1374 |
+
let pred2 = na.predizer(&zc2, 0, None)?;
|
| 1375 |
+
let dif2 = (&pred2 - &zt2)?;
|
| 1376 |
+
perdas.push((dif2.sqr()?.sum_all()? / cfg.d_emb as f64)?);
|
| 1377 |
+
}
|
| 1378 |
+
let l_pred = (Tensor::stack(&perdas, 0)?.sum_all()? / perdas.len() as f64)?;
|
| 1379 |
+
let l_vc = (nb.perda_vc(&z_a)? + na.perda_vc(&z_b)?)?;
|
| 1380 |
+
let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(cfg.lambda_vc, 0.0)?)?;
|
| 1381 |
+
let p = total.to_scalar::<f32>()? as f64;
|
| 1382 |
+
if p.is_finite() {
|
| 1383 |
+
if epoca == 0 {
|
| 1384 |
+
perda_inicial = p;
|
| 1385 |
+
}
|
| 1386 |
+
perda_final = p;
|
| 1387 |
+
}
|
| 1388 |
+
let mut grads = crate::treino::grads_achatados(&total, &[nb.vars(), na.vars()].concat())?;
|
| 1389 |
+
crate::treino::AdamWManual::clipar(&mut grads, 1.0);
|
| 1390 |
+
otim.passo(grads);
|
| 1391 |
+
nb.ema_passo()?;
|
| 1392 |
+
na.ema_passo()?;
|
| 1393 |
+
}
|
| 1394 |
+
let z1 = nb.codificar(mod_a)?;
|
| 1395 |
+
Ok(RelatorioJepa {
|
| 1396 |
+
variante: "MC-JEPA".into(),
|
| 1397 |
+
perda_inicial,
|
| 1398 |
+
perda_final,
|
| 1399 |
+
perda_pred_inicial: perda_inicial,
|
| 1400 |
+
perda_pred_final: perda_final,
|
| 1401 |
+
std_emb_final: std_por_dim(&z1)?,
|
| 1402 |
+
std_emb_inicial: std_por_dim(&nb.codificar(mod_a)?)?,
|
| 1403 |
+
n_patches: n,
|
| 1404 |
+
d_patch: d_a.max(d_b),
|
| 1405 |
+
epocas: cfg.epocas,
|
| 1406 |
+
})
|
| 1407 |
+
}
|
| 1408 |
+
|
| 1409 |
+
/// **BJEPA** — bidirecional: preditor causal (z_t → z̃_{t+1}), anti-causal
|
| 1410 |
+
/// (z_{t+1} → z̃_t) e consistência de ciclo
|
| 1411 |
+
/// `‖P_b(P_f(z_t)) − sg(z_t)‖²` — a volta deve reconstruir o presente.
|
| 1412 |
+
pub fn treinar_bjepa(patches: &Tensor, cfg: &CfgJepa, dev: &Device) -> Result<RelatorioJepa> {
|
| 1413 |
+
let n = patches.dim(0)?;
|
| 1414 |
+
let d_patch = patches.dim(1)?;
|
| 1415 |
+
let mut nucleo = NucleoJepa::novo_com_semente(
|
| 1416 |
+
d_patch, cfg.d_emb, 0, 2, cfg.m, cfg.lambda_vc, cfg.gamma_vc, cfg.semente, dev,
|
| 1417 |
+
)?;
|
| 1418 |
+
// preditor anti-causal: núcleo separado
|
| 1419 |
+
let mut ninv = NucleoJepa::novo_com_semente(
|
| 1420 |
+
d_patch, cfg.d_emb, 0, 2, cfg.m, cfg.lambda_vc, cfg.gamma_vc, cfg.semente, dev,
|
| 1421 |
+
)?;
|
| 1422 |
+
let mut otim =
|
| 1423 |
+
crate::treino::AdamWManual::novo([nucleo.vars(), ninv.vars()].concat(), cfg.lr, 0.01);
|
| 1424 |
+
let mut perda_inicial = f64::NAN;
|
| 1425 |
+
let mut perda_final = f64::NAN;
|
| 1426 |
+
for epoca in 0..cfg.epocas {
|
| 1427 |
+
let z = nucleo.codificar(patches)?; // (n, d_emb)
|
| 1428 |
+
let zt = nucleo.alvo_codificar(patches)?; // alvos EMA (stop-grad)
|
| 1429 |
+
let mut perdas: Vec<Tensor> = Vec::new();
|
| 1430 |
+
for t in 0..n.saturating_sub(1) {
|
| 1431 |
+
let zc = z.get(t)?.broadcast_as((1, cfg.d_emb))?;
|
| 1432 |
+
let z_prox = zt.get(t + 1)?.broadcast_as((1, cfg.d_emb))?;
|
| 1433 |
+
// causal: z_t → z̃_{t+1} (pos 0 = "próximo")
|
| 1434 |
+
let pf = nucleo.predizer(&zc, 0, None)?;
|
| 1435 |
+
let dif = (&pf - &z_prox)?;
|
| 1436 |
+
perdas.push((dif.sqr()?.sum_all()? / cfg.d_emb as f64)?);
|
| 1437 |
+
// anti-causal: z_{t+1} → z̃_t (pos 1 = "anterior")
|
| 1438 |
+
let zb = z.get(t + 1)?.broadcast_as((1, cfg.d_emb))?;
|
| 1439 |
+
let z_ant = zt.get(t)?.broadcast_as((1, cfg.d_emb))?;
|
| 1440 |
+
let pb = ninv.predizer(&zb, 1, None)?;
|
| 1441 |
+
let dif2 = (&pb - &z_ant)?;
|
| 1442 |
+
perdas.push((dif2.sqr()?.sum_all()? / cfg.d_emb as f64)?);
|
| 1443 |
+
// ciclo: P_b(P_f(z_t)) ≈ sg(z_t)
|
| 1444 |
+
let pf_sg = nucleo.predizer(&zc, 0, None)?.detach();
|
| 1445 |
+
let pciclo = ninv.predizer(&pf_sg.broadcast_as((1, cfg.d_emb))?, 1, None)?;
|
| 1446 |
+
let zt_sg = zt.get(t)?.broadcast_as((1, cfg.d_emb))?;
|
| 1447 |
+
let dif3 = (&pciclo - &zt_sg)?;
|
| 1448 |
+
perdas.push((dif3.sqr()?.sum_all()? / cfg.d_emb as f64)?);
|
| 1449 |
+
}
|
| 1450 |
+
let l_pred = (Tensor::stack(&perdas, 0)?.sum_all()? / perdas.len().max(1) as f64)?;
|
| 1451 |
+
let l_vc = nucleo.perda_vc(&z)?;
|
| 1452 |
+
let total = (l_pred.affine(1.0, 0.0)? + l_vc.affine(cfg.lambda_vc, 0.0)?)?;
|
| 1453 |
+
let p = total.to_scalar::<f32>()? as f64;
|
| 1454 |
+
if p.is_finite() {
|
| 1455 |
+
if epoca == 0 {
|
| 1456 |
+
perda_inicial = p;
|
| 1457 |
+
}
|
| 1458 |
+
perda_final = p;
|
| 1459 |
+
}
|
| 1460 |
+
let mut grads = crate::treino::grads_achatados(&total, &[nucleo.vars(), ninv.vars()].concat())?;
|
| 1461 |
+
crate::treino::AdamWManual::clipar(&mut grads, 1.0);
|
| 1462 |
+
otim.passo(grads);
|
| 1463 |
+
nucleo.ema_passo()?;
|
| 1464 |
+
ninv.ema_passo()?;
|
| 1465 |
+
}
|
| 1466 |
+
let z1 = nucleo.codificar(patches)?;
|
| 1467 |
+
Ok(RelatorioJepa {
|
| 1468 |
+
variante: "BJEPA".into(),
|
| 1469 |
+
perda_inicial,
|
| 1470 |
+
perda_final,
|
| 1471 |
+
perda_pred_inicial: perda_inicial,
|
| 1472 |
+
perda_pred_final: perda_final,
|
| 1473 |
+
std_emb_final: std_por_dim(&z1)?,
|
| 1474 |
+
std_emb_inicial: std_por_dim(&nucleo.codificar(patches)?)?,
|
| 1475 |
+
n_patches: n,
|
| 1476 |
+
d_patch,
|
| 1477 |
+
epocas: cfg.epocas,
|
| 1478 |
+
})
|
| 1479 |
+
}
|
| 1480 |
+
|
| 1481 |
+
/// Esquema de máscara por variante a partir da grade de patches.
|
| 1482 |
+
pub fn mascara_por_variante(
|
| 1483 |
+
variante: VarianteJepa,
|
| 1484 |
+
grid: &[usize],
|
| 1485 |
+
frac: f64,
|
| 1486 |
+
rng: &mut RngDeterministico,
|
| 1487 |
+
) -> Vec<bool> {
|
| 1488 |
+
match variante {
|
| 1489 |
+
VarianteJepa::ImageJepa => mascara_blocos_2d(grid[0], grid.get(1).copied().unwrap_or(1), 4, frac, rng),
|
| 1490 |
+
VarianteJepa::VJepa2 | VarianteJepa::Jepa3D => mascara_tubos_3d(
|
| 1491 |
+
grid[0],
|
| 1492 |
+
grid.get(1).copied().unwrap_or(1),
|
| 1493 |
+
grid.get(2).copied().unwrap_or(1),
|
| 1494 |
+
3,
|
| 1495 |
+
frac,
|
| 1496 |
+
rng,
|
| 1497 |
+
),
|
| 1498 |
+
VarianteJepa::AudioJepa => mascara_bandas(grid[0], grid.get(1).copied().unwrap_or(1), 3, frac, rng),
|
| 1499 |
+
VarianteJepa::BrainJepa => mascara_segmentos(grid[0], grid.get(1).copied().unwrap_or(1), frac, rng),
|
| 1500 |
+
_ => mascara_intervalos_1d(grid.iter().product::<usize>().max(1), frac, rng),
|
| 1501 |
+
}
|
| 1502 |
+
}
|
| 1503 |
+
|
| 1504 |
+
pub fn versao() -> &'static str {
|
| 1505 |
+
"jepa-0.5.0"
|
| 1506 |
+
}
|
| 1507 |
+
|
| 1508 |
+
#[cfg(test)]
|
| 1509 |
+
mod testes {
|
| 1510 |
+
use super::*;
|
| 1511 |
+
|
| 1512 |
+
fn tensor_2d(dados: &[Vec<f64>], dev: &Device) -> Tensor {
|
| 1513 |
+
let flat: Vec<f32> = dados.iter().flat_map(|v| v.iter().map(|&x| x as f32)).collect();
|
| 1514 |
+
Tensor::from_vec(flat, (dados.len(), dados[0].len()), dev).unwrap()
|
| 1515 |
+
}
|
| 1516 |
+
|
| 1517 |
+
|
| 1518 |
+
/// T-J3 (falsificação da neutralidade): com UM único braço (frac fixa),
|
| 1519 |
+
/// o treino adaptativo deve ser BIT-A-BIT idêntico ao `treinar_variante`
|
| 1520 |
+
/// com a mesma fração — o RNG do bandit é fluxo separado e não toca o
|
| 1521 |
+
/// fluxo de máscara. Qualquer vazamento de aleatoriedade quebra a igualdade.
|
| 1522 |
+
#[test]
|
| 1523 |
+
fn jepa_adaptativa_um_braco_e_fixa() {
|
| 1524 |
+
let dev = Device::Cpu;
|
| 1525 |
+
let mut rng = RngDeterministico::novo(3);
|
| 1526 |
+
let dados: Vec<Vec<f64>> = (0..96)
|
| 1527 |
+
.map(|_| (0..12).map(|_| rng.normal(0.0, 1.0)).collect())
|
| 1528 |
+
.collect();
|
| 1529 |
+
let t = tensor_2d(&dados, &dev);
|
| 1530 |
+
let mut cfg = CfgJepa::default();
|
| 1531 |
+
cfg.epocas = 12;
|
| 1532 |
+
cfg.d_emb = 16;
|
| 1533 |
+
cfg.frac_mascara = 0.35;
|
| 1534 |
+
let grid = [12usize, 8];
|
| 1535 |
+
let var = VarianteJepa::ImageJepa;
|
| 1536 |
+
// fixo
|
| 1537 |
+
let rel_fixo = treinar_variante(
|
| 1538 |
+
&t,
|
| 1539 |
+
&|_n, r| mascara_por_variante(var, &grid, 0.35, r),
|
| 1540 |
+
&cfg,
|
| 1541 |
+
var,
|
| 1542 |
+
None,
|
| 1543 |
+
&dev,
|
| 1544 |
+
)
|
| 1545 |
+
.unwrap();
|
| 1546 |
+
// adaptativo com um braço = mesma fração
|
| 1547 |
+
let rel_adapt = treinar_variante_adaptativa(
|
| 1548 |
+
&t,
|
| 1549 |
+
&grid,
|
| 1550 |
+
&[0.35],
|
| 1551 |
+
&cfg,
|
| 1552 |
+
var,
|
| 1553 |
+
None,
|
| 1554 |
+
&dev,
|
| 1555 |
+
)
|
| 1556 |
+
.unwrap();
|
| 1557 |
+
assert_eq!(
|
| 1558 |
+
rel_fixo.perda_pred_final.to_bits(),
|
| 1559 |
+
rel_adapt.base.perda_pred_final.to_bits(),
|
| 1560 |
+
"perda preditiva divergiu: fixa {} vs adapt {}",
|
| 1561 |
+
rel_fixo.perda_pred_final,
|
| 1562 |
+
rel_adapt.base.perda_pred_final
|
| 1563 |
+
);
|
| 1564 |
+
assert_eq!(rel_fixo.perda_final.to_bits(), rel_adapt.base.perda_final.to_bits());
|
| 1565 |
+
assert_eq!(rel_adapt.jogadas_por_braco, vec![cfg.epocas]);
|
| 1566 |
+
// determinismo do adaptativo multi-braço
|
| 1567 |
+
let roda = || {
|
| 1568 |
+
treinar_variante_adaptativa(&t, &grid, &[0.2, 0.35, 0.5], &cfg, var, None, &dev)
|
| 1569 |
+
.unwrap()
|
| 1570 |
+
};
|
| 1571 |
+
let a1 = roda();
|
| 1572 |
+
let a2 = roda();
|
| 1573 |
+
assert_eq!(a1.base.perda_pred_final.to_bits(), a2.base.perda_pred_final.to_bits());
|
| 1574 |
+
assert_eq!(a1.escolhas.len(), a2.escolhas.len());
|
| 1575 |
+
// multi-braço joga todos os braços ao menos uma vez (exploração TS)
|
| 1576 |
+
assert!(a1.jogadas_por_braco.iter().all(|&j| j > 0), "braços não explorados: {:?}", a1.jogadas_por_braco);
|
| 1577 |
+
}
|
| 1578 |
+
|
| 1579 |
+
#[test]
|
| 1580 |
+
fn teste_mascaras_validas() {
|
| 1581 |
+
// fração respeitada ±, particionamento contexto/alvo não-vazio
|
| 1582 |
+
let mut rng = RngDeterministico::novo(1);
|
| 1583 |
+
let m2 = mascara_blocos_2d(8, 8, 4, 0.3, &mut rng);
|
| 1584 |
+
assert_eq!(m2.len(), 64);
|
| 1585 |
+
let frac = m2.iter().filter(|&&b| b).count() as f64 / 64.0;
|
| 1586 |
+
assert!(frac >= 0.15 && frac <= 0.55, "frac 2d = {frac}");
|
| 1587 |
+
assert!(m2.iter().any(|&b| !b), "contexto vazio");
|
| 1588 |
+
|
| 1589 |
+
let m3 = mascara_tubos_3d(4, 6, 6, 3, 0.3, &mut rng);
|
| 1590 |
+
assert_eq!(m3.len(), 144);
|
| 1591 |
+
let f3 = m3.iter().filter(|&&b| b).count() as f64 / 144.0;
|
| 1592 |
+
assert!(f3 >= 0.15 && f3 <= 0.6, "frac 3d = {f3}");
|
| 1593 |
+
|
| 1594 |
+
let ms = mascara_segmentos(4, 32, 0.25, &mut rng);
|
| 1595 |
+
assert_eq!(ms.len(), 128);
|
| 1596 |
+
assert!(ms.iter().any(|&b| b) && ms.iter().any(|&b| !b));
|
| 1597 |
+
|
| 1598 |
+
let m1 = mascara_intervalos_1d(64, 0.3, &mut rng);
|
| 1599 |
+
let f1 = m1.iter().filter(|&&b| b).count() as f64 / 64.0;
|
| 1600 |
+
assert!(f1 >= 0.25 && f1 <= 0.45, "frac 1d = {f1}");
|
| 1601 |
+
}
|
| 1602 |
+
|
| 1603 |
+
#[test]
|
| 1604 |
+
fn teste_fps_cobre() {
|
| 1605 |
+
let mut rng = RngDeterministico::novo(2);
|
| 1606 |
+
let pontos: Vec<Vec<f64>> = (0..40)
|
| 1607 |
+
.map(|i| {
|
| 1608 |
+
let a = i as f64 * 0.1;
|
| 1609 |
+
vec![a.cos() * 3.0, a.sin() * 3.0, (i % 7) as f64 * 0.3]
|
| 1610 |
+
})
|
| 1611 |
+
.collect();
|
| 1612 |
+
let centros = pontos_mais_distantes(&pontos, 6, &mut rng);
|
| 1613 |
+
assert_eq!(centros.len(), 6);
|
| 1614 |
+
// centros distintos
|
| 1615 |
+
let mut s = centros.clone();
|
| 1616 |
+
s.sort();
|
| 1617 |
+
s.dedup();
|
| 1618 |
+
assert_eq!(s.len(), 6);
|
| 1619 |
+
}
|
| 1620 |
+
|
| 1621 |
+
#[test]
|
| 1622 |
+
fn teste_j1_colapso_impossivel() {
|
| 1623 |
+
// No colapso total (z constante), a perda VC = γ² > 0 — verificando a
|
| 1624 |
+
// fórmula implementada com um lote constante.
|
| 1625 |
+
let dev = Device::Cpu;
|
| 1626 |
+
let nucleo = NucleoJepa::novo(4, 8, 0, 4, 0.99, 1.0, 1.0, &dev).unwrap();
|
| 1627 |
+
let z = Tensor::from_vec(vec![0.5f32; 8 * 8], (8, 8), &dev).unwrap();
|
| 1628 |
+
let lvc = nucleo.perda_vc(&z).unwrap();
|
| 1629 |
+
let v = lvc.to_scalar::<f32>().unwrap() as f64;
|
| 1630 |
+
// std=0 ⇒ dobradiça = γ = 1 ⇒ V/d = 1; cov = 0
|
| 1631 |
+
assert!(v > 0.9, "perda VC no colapso = {v} (deveria ser ~1)");
|
| 1632 |
+
}
|
| 1633 |
+
|
| 1634 |
+
#[test]
|
| 1635 |
+
fn teste_j2_ema_ponto_fixo() {
|
| 1636 |
+
// encoder congelado ⇒ após muitas passadas EMA, alvo = encoder
|
| 1637 |
+
let dev = Device::Cpu;
|
| 1638 |
+
let mut nucleo = NucleoJepa::novo(4, 8, 0, 4, 0.5, 0.1, 1.0, &dev).unwrap();
|
| 1639 |
+
for _ in 0..40 {
|
| 1640 |
+
nucleo.ema_passo().unwrap();
|
| 1641 |
+
}
|
| 1642 |
+
let a = nucleo.alvo.vars()[0].as_tensor().flatten_all().unwrap().to_vec1::<f32>().unwrap();
|
| 1643 |
+
let b = nucleo.encoder.vars()[0].as_tensor().flatten_all().unwrap().to_vec1::<f32>().unwrap();
|
| 1644 |
+
let dif: f32 = a.iter().zip(&b).map(|(x, y)| (x - y).abs()).fold(0.0, f32::max);
|
| 1645 |
+
assert!(dif < 1e-5, "EMA não convergiu: dif máx = {dif}");
|
| 1646 |
+
}
|
| 1647 |
+
|
| 1648 |
+
#[test]
|
| 1649 |
+
fn teste_jepa_aprende_latente_previsivel() {
|
| 1650 |
+
// J-3: com alvo previsível (função determinística do contexto), a
|
| 1651 |
+
// perda pred must cair substancialmente e o std das incorporações
|
| 1652 |
+
// NÃO colapsa (J-1).
|
| 1653 |
+
let dev = Device::Cpu;
|
| 1654 |
+
let mut rng = RngDeterministico::novo(3);
|
| 1655 |
+
// patches = 64 vetores 8-D em 4 grupos (estrutura previsível)
|
| 1656 |
+
let mut dados: Vec<Vec<f64>> = Vec::new();
|
| 1657 |
+
for g in 0..4 {
|
| 1658 |
+
let c = g as f64 * 2.0;
|
| 1659 |
+
for _ in 0..16 {
|
| 1660 |
+
dados.push((0..8).map(|k| c + rng.normal(0.0, 0.3) + (k as f64) * 0.05).collect());
|
| 1661 |
+
}
|
| 1662 |
+
}
|
| 1663 |
+
let patches = tensor_2d(&dados, &dev);
|
| 1664 |
+
let cfg = CfgJepa { d_emb: 16, epocas: 40, lr: 5e-3, ..Default::default() };
|
| 1665 |
+
let rel = treinar_variante(
|
| 1666 |
+
&patches,
|
| 1667 |
+
&|n, r| mascara_intervalos_1d(n, 0.3, r),
|
| 1668 |
+
&cfg,
|
| 1669 |
+
VarianteJepa::TJepa,
|
| 1670 |
+
None,
|
| 1671 |
+
&dev,
|
| 1672 |
+
)
|
| 1673 |
+
.unwrap();
|
| 1674 |
+
assert!(
|
| 1675 |
+
rel.perda_final < rel.perda_inicial * 0.8,
|
| 1676 |
+
"perda deveria cair: {} → {}",
|
| 1677 |
+
rel.perda_inicial,
|
| 1678 |
+
rel.perda_final
|
| 1679 |
+
);
|
| 1680 |
+
assert!(
|
| 1681 |
+
rel.std_emb_final > 0.05,
|
| 1682 |
+
"sem colapso: std_final = {}",
|
| 1683 |
+
rel.std_emb_final
|
| 1684 |
+
);
|
| 1685 |
+
}
|
| 1686 |
+
|
| 1687 |
+
#[test]
|
| 1688 |
+
fn teste_adaptadores_formas() {
|
| 1689 |
+
let dev = Device::Cpu;
|
| 1690 |
+
let img = Tensor::randn(0f32, 1.0, (3, 16, 16), &dev).unwrap();
|
| 1691 |
+
let p = em_patches_2d(&img, 4, 4).unwrap();
|
| 1692 |
+
assert_eq!(p.dims().to_vec(), vec![16, 48]);
|
| 1693 |
+
|
| 1694 |
+
let vid = Tensor::randn(0f32, 1.0, (4, 8, 8), &dev).unwrap();
|
| 1695 |
+
let t3 = em_tubos_3d(&vid, 2, 4, 4).unwrap();
|
| 1696 |
+
assert_eq!(t3.dims().to_vec(), vec![8, 32]);
|
| 1697 |
+
|
| 1698 |
+
let spec = Tensor::randn(0f32, 1.0, (16, 32), &dev).unwrap();
|
| 1699 |
+
let ps = em_patches_espectrograma(&spec, 4, 8).unwrap();
|
| 1700 |
+
assert_eq!(ps.dims().to_vec(), vec![16, 32]);
|
| 1701 |
+
|
| 1702 |
+
let ts = Tensor::randn(0f32, 1.0, (4, 32), &dev).unwrap();
|
| 1703 |
+
let pt_ = em_patches_serie(&ts, 8).unwrap();
|
| 1704 |
+
assert_eq!(pt_.dims().to_vec(), vec![16, 8]);
|
| 1705 |
+
|
| 1706 |
+
let vol = Tensor::randn(0f32, 1.0, (8, 8, 8), &dev).unwrap();
|
| 1707 |
+
let vx = em_voxels(&vol, 4, 4, 4).unwrap();
|
| 1708 |
+
assert_eq!(vx.dims().to_vec(), vec![8, 64]);
|
| 1709 |
+
|
| 1710 |
+
let pc = Tensor::randn(0f32, 1.0, (50, 3), &dev).unwrap();
|
| 1711 |
+
let mut rng = RngDeterministico::novo(5);
|
| 1712 |
+
let gr = em_grupos_pontos(&pc, 6, 4, &mut rng).unwrap();
|
| 1713 |
+
assert_eq!(gr.dims().to_vec(), vec![6, 12]);
|
| 1714 |
+
}
|
| 1715 |
+
|
| 1716 |
+
#[test]
|
| 1717 |
+
fn teste_sonic_moe_capacidade_e_balanceamento() {
|
| 1718 |
+
// SM-1: carga por especialista ≤ capacidade; SM-2: aux ≥ ~1 quando
|
| 1719 |
+
// roteador é ~uniforme (início).
|
| 1720 |
+
let dev = Device::Cpu;
|
| 1721 |
+
let moe = SonicMoe::novo(8, 4, 2, 6, &dev).unwrap();
|
| 1722 |
+
let x = Tensor::randn(0f32, 1.0, (20, 8), &dev).unwrap();
|
| 1723 |
+
let s = moe.fwd(&x).unwrap();
|
| 1724 |
+
for (e, c) in s.carga.iter().enumerate() {
|
| 1725 |
+
assert!(*c <= 6, "especialista {e} com carga {c} > capacidade 6");
|
| 1726 |
+
}
|
| 1727 |
+
assert_eq!(s.saida.dims().to_vec(), vec![20, 8]);
|
| 1728 |
+
let aux = s.perda_aux.to_scalar::<f32>().unwrap() as f64;
|
| 1729 |
+
// com roteador quase uniforme, aux ≈ E·Σ (1/E)·(1/E) = 1
|
| 1730 |
+
assert!(aux > 0.5 && aux < 3.0, "aux = {aux}");
|
| 1731 |
+
// cada token processado por ≤ n_top especialistas
|
| 1732 |
+
let total_carga: usize = s.carga.iter().sum();
|
| 1733 |
+
assert!(total_carga <= 20 * 2, "carga total {total_carga} > n·topk");
|
| 1734 |
+
}
|
| 1735 |
+
|
| 1736 |
+
#[test]
|
| 1737 |
+
fn teste_replay_ssm_estabilidade_e_replay() {
|
| 1738 |
+
// R-1: estados finitos e limitados; R-2: com replay α>0, após treinar
|
| 1739 |
+
// a tarefa nova, a dinâmica antiga (magnitude do estado) permanece
|
| 1740 |
+
// estável (não diverge).
|
| 1741 |
+
let dev = Device::Cpu;
|
| 1742 |
+
let mut ssm = ReplaySsm::novo(4, 6, 0.5, 8, &dev).unwrap();
|
| 1743 |
+
let vars = ssm.vars();
|
| 1744 |
+
let mut otim = crate::treino::AdamWManual::novo(vars, 1e-2, 0.0);
|
| 1745 |
+
|
| 1746 |
+
// tarefa "antiga": sinal constante 1.0
|
| 1747 |
+
let x_antigo = Tensor::from_vec(vec![1.0f32; 16 * 4], (16, 4), &dev).unwrap();
|
| 1748 |
+
for _ in 0..30 {
|
| 1749 |
+
let h = ssm.scan(&x_antigo).unwrap();
|
| 1750 |
+
let alvo = h.detach();
|
| 1751 |
+
ssm.passo_treino(&x_antigo, &alvo, &mut otim).unwrap();
|
| 1752 |
+
}
|
| 1753 |
+
// estado após treino antigo: finito
|
| 1754 |
+
let h1 = ssm.scan(&x_antigo).unwrap();
|
| 1755 |
+
let m1 = h1.abs().unwrap().max_all().unwrap().to_scalar::<f32>().unwrap();
|
| 1756 |
+
assert!(m1.is_finite() && m1 < 50.0, "estado explodiu: {m1}");
|
| 1757 |
+
|
| 1758 |
+
// tarefa "nova": sinal −1.0 — treino com replay
|
| 1759 |
+
let x_novo = Tensor::from_vec(vec![-1.0f32; 16 * 4], (16, 4), &dev).unwrap();
|
| 1760 |
+
for _ in 0..30 {
|
| 1761 |
+
let h = ssm.scan(&x_novo).unwrap();
|
| 1762 |
+
let alvo = h.detach();
|
| 1763 |
+
ssm.passo_treino(&x_novo, &alvo, &mut otim).unwrap();
|
| 1764 |
+
}
|
| 1765 |
+
// a dinâmica antiga continua estável (sem explosão)
|
| 1766 |
+
let h2 = ssm.scan(&x_antigo).unwrap();
|
| 1767 |
+
let m2 = h2.abs().unwrap().max_all().unwrap().to_scalar::<f32>().unwrap();
|
| 1768 |
+
assert!(m2.is_finite() && m2 < 50.0, "estado antigo explodiu: {m2}");
|
| 1769 |
+
// portas dentro de (0,1)
|
| 1770 |
+
let a = ssm.a.as_tensor().to_vec1::<f32>().unwrap();
|
| 1771 |
+
for v in a {
|
| 1772 |
+
assert!(v.is_finite());
|
| 1773 |
+
}
|
| 1774 |
+
}
|
| 1775 |
+
}
|
src/lib.rs
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/moe_redirecionavel.rs
ADDED
|
@@ -0,0 +1,1202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! **MoEs de codificação/decodificação REDIRECIONÁVEIS com Mecanismo de
|
| 2 |
+
//! Atenção próprio e independente (paralelo)** (v0.6.0 — pedido do usuário).
|
| 3 |
+
//!
|
| 4 |
+
//! Cada `MoeRedirecionavel` possui:
|
| 5 |
+
//! - um roteador aprendido `d → E`;
|
| 6 |
+
//! - E especialistas `MlpJepa`, cada um com um PAPEL (Codificador ou
|
| 7 |
+
//! Decodificador) — o conjunto de papéis é REDIRECIONÁVEL em tempo de
|
| 8 |
+
//! execução (permutação), sem reconstrução do modelo;
|
| 9 |
+
//! - uma ATENÇÃO PRÓPRIA independente da atenção ADBA do tronco, com gate
|
| 10 |
+
//! escalar nascido em ZERO (neutralidade no nascimento);
|
| 11 |
+
//! - capacidade por especialista e perda auxiliar de balanceamento.
|
| 12 |
+
//!
|
| 13 |
+
//! **Provas completas em `docs/memorias/r-25-*`; síntese:**
|
| 14 |
+
//! - **M-R1 (invariância sob redirecionamento):** o redirecionamento é uma
|
| 15 |
+
//! permutação π dos papéis; o multiconjunto de atribuições
|
| 16 |
+
//! (especialista, token) numa passada por papel é levado ao multiconjunto
|
| 17 |
+
//! da passada π(papel) — a capacidade total e a perda auxiliar de
|
| 18 |
+
//! balanceamento E·Σ f_e·p_e são invariantes (soma sobre todos os e).
|
| 19 |
+
//! - **M-R2 (neutralidade da atenção própria no nascimento):** com
|
| 20 |
+
//! `g = 0`, `y = x` bit-a-bit; a perturbação é limitada por
|
| 21 |
+
//! `‖Δy‖ ≤ L_soft·|g|·‖W_o·V‖` (Lipschitz do softmax com constante
|
| 22 |
+
//! L_soft = 1 em probabilidade; estrutura idêntica ao T-P5).
|
| 23 |
+
//! - **M-R3 (paralelismo dos dois caminhos):** numa mesma entrada, as
|
| 24 |
+
//! passadas Codificador e Decodificador tocam conjuntos DISJUNTOS de
|
| 25 |
+
//! especialistas e compartilham apenas leitura — o DAG de computação tem
|
| 26 |
+
//! duas sub-DAGs independentes ⇒ tempo paralelo (2 processadores) =
|
| 27 |
+
//! max(T_enc, T_dec) + sobrecarga < T_enc + T_dec = tempo sequencial
|
| 28 |
+
//! (modelo PRAM; medido empiricamente com threads).
|
| 29 |
+
//! - **M-R4 (fechamento da classe de funções):** redirecionar equivale a
|
| 30 |
+
//! permutar colunas do roteador e re-rotular papéis; a classe de funções
|
| 31 |
+
//! implementáveis é fechada sob π ⇒ expressividade inalterada.
|
| 32 |
+
//! - **M-R5 (water-filling por QE — Cauchy–Schwarz):** dadas capacidades
|
| 33 |
+
//! totais C e erros por célula q_i, a alocação `c_i ∝ q_i` minimiza
|
| 34 |
+
//! `Σ q_i²/c_i ≥ (Σq_i)²/C`, cota superior do erro refinado ponderado.
|
| 35 |
+
|
| 36 |
+
use candle_core::{Device, Result, Tensor, Var};
|
| 37 |
+
use crate::camadas::Linear;
|
| 38 |
+
use crate::jepa::MlpJepa;
|
| 39 |
+
use crate::rng::RngDeterministico;
|
| 40 |
+
use crate::treino::AdamWManual;
|
| 41 |
+
|
| 42 |
+
// ---------------------------------------------------------------------------
|
| 43 |
+
// Atenção própria (independente e paralela à ADBA do tronco)
|
| 44 |
+
// ---------------------------------------------------------------------------
|
| 45 |
+
|
| 46 |
+
pub struct AtencaoPropria {
|
| 47 |
+
pub q: Linear,
|
| 48 |
+
pub k: Linear,
|
| 49 |
+
pub v: Linear,
|
| 50 |
+
pub o: Linear,
|
| 51 |
+
/// gate escalar — nasce ZERO (M-R2: neutralidade no nascimento).
|
| 52 |
+
pub gate: Var,
|
| 53 |
+
pub n_cabecas: usize,
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
impl AtencaoPropria {
|
| 57 |
+
pub fn novo(d: usize, n_cabecas: usize, dev: &Device) -> Result<Self> {
|
| 58 |
+
let desvio = 1.0 / ((d * 2) as f64).sqrt();
|
| 59 |
+
let mk = || Linear::sem_vies(d, d, desvio, dev);
|
| 60 |
+
let gate = Tensor::zeros((), candle_core::DType::F32, dev)?;
|
| 61 |
+
Ok(AtencaoPropria {
|
| 62 |
+
q: mk()?, k: mk()?, v: mk()?, o: mk()?,
|
| 63 |
+
gate: Var::from_tensor(&gate)?,
|
| 64 |
+
n_cabecas,
|
| 65 |
+
})
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
/// x (n, d) → y = x + g·attn(x) — multi-cabeça compacta (sem máscara:
|
| 69 |
+
/// atenção plena entre os tokens da entrada do MoE).
|
| 70 |
+
pub fn fwd(&self, x: &Tensor) -> Result<Tensor> {
|
| 71 |
+
let d = x.dim(candle_core::D::Minus1)?;
|
| 72 |
+
let d_cab = d / self.n_cabecas;
|
| 73 |
+
let n = x.dim(0)?;
|
| 74 |
+
let forma = (self.n_cabecas, n, d_cab);
|
| 75 |
+
let q = self.q.fwd(x)?.reshape(forma)?;
|
| 76 |
+
let k = self.k.fwd(x)?.reshape(forma)?;
|
| 77 |
+
let v = self.v.fwd(x)?.reshape(forma)?;
|
| 78 |
+
let scores = q.matmul(&k.transpose(1, 2)?)?.affine(1.0 / (d_cab as f64).sqrt(), 0.0)?;
|
| 79 |
+
let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1)?;
|
| 80 |
+
let ctx = attn.matmul(&v)?; // (h, n, d_cab)
|
| 81 |
+
let ctx = ctx.reshape((n, d))?;
|
| 82 |
+
let saida = self.o.fwd(&ctx)?;
|
| 83 |
+
// y = x + g·saida (g=0 no nascimento ⇒ y = x — M-R2)
|
| 84 |
+
let g = self.gate.as_tensor();
|
| 85 |
+
let forma: Vec<usize> = saida.dims().to_vec();
|
| 86 |
+
let delta = saida.broadcast_mul(&g.broadcast_as(forma)?)?;
|
| 87 |
+
Ok((x + &delta)?)
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
pub fn vars(&self) -> Vec<Var> {
|
| 91 |
+
let mut v = self.q.vars();
|
| 92 |
+
v.extend(self.k.vars());
|
| 93 |
+
v.extend(self.v.vars());
|
| 94 |
+
v.extend(self.o.vars());
|
| 95 |
+
v.push(self.gate.clone());
|
| 96 |
+
v
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
// ---------------------------------------------------------------------------
|
| 101 |
+
// MoE redirecionável
|
| 102 |
+
// ---------------------------------------------------------------------------
|
| 103 |
+
|
| 104 |
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
| 105 |
+
pub enum Papel {
|
| 106 |
+
Codificador,
|
| 107 |
+
Decodificador,
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
impl Papel {
|
| 111 |
+
pub fn nome(&self) -> &'static str {
|
| 112 |
+
match self { Papel::Codificador => "codificador", Papel::Decodificador => "decodificador" }
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
pub struct SaidaMoeR {
|
| 117 |
+
pub saida: Tensor,
|
| 118 |
+
pub perda_aux: Tensor,
|
| 119 |
+
pub carga: Vec<usize>,
|
| 120 |
+
pub descartados: usize,
|
| 121 |
+
/// especialistas efetivamente usados nesta passada (índices globais).
|
| 122 |
+
pub usados: Vec<usize>,
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
pub struct MoeRedirecionavel {
|
| 126 |
+
pub roteador: Linear, // d → E
|
| 127 |
+
pub especialistas: Vec<MlpJepa>, // E experts d → d
|
| 128 |
+
pub papeis: Vec<Papel>, // papel corrente de cada especialista
|
| 129 |
+
pub atencao: AtencaoPropria, // atenção PRÓPRIA (M-R2/M-R3)
|
| 130 |
+
pub n_top: usize,
|
| 131 |
+
pub capacidade: usize,
|
| 132 |
+
pub lambda_aux: f64,
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
impl MoeRedirecionavel {
|
| 136 |
+
pub fn novo(d: usize, n_expertos: usize, n_top: usize, capacidade: usize, dev: &Device) -> Result<Self> {
|
| 137 |
+
let roteador = Linear::sem_vies(d, n_expertos, 0.1, dev)?;
|
| 138 |
+
let especialistas = (0..n_expertos)
|
| 139 |
+
.map(|_| MlpJepa::novo(d, 2 * d, d, dev))
|
| 140 |
+
.collect::<Result<Vec<_>>>()?;
|
| 141 |
+
// papéis iniciais alternados (metade cod, metade dec)
|
| 142 |
+
let papeis = (0..n_expertos)
|
| 143 |
+
.map(|i| if i % 2 == 0 { Papel::Codificador } else { Papel::Decodificador })
|
| 144 |
+
.collect();
|
| 145 |
+
Ok(MoeRedirecionavel {
|
| 146 |
+
roteador,
|
| 147 |
+
especialistas,
|
| 148 |
+
papeis,
|
| 149 |
+
atencao: AtencaoPropria::novo(d, 2, dev)?,
|
| 150 |
+
n_top: n_top.max(1),
|
| 151 |
+
capacidade,
|
| 152 |
+
lambda_aux: 0.01,
|
| 153 |
+
})
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
pub fn vars(&self) -> Vec<Var> {
|
| 157 |
+
let mut v = self.roteador.vars();
|
| 158 |
+
for e in &self.especialistas {
|
| 159 |
+
v.extend(e.vars());
|
| 160 |
+
}
|
| 161 |
+
v.extend(self.atencao.vars());
|
| 162 |
+
v
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
/// **M-R1/M-R4:** redireciona os papéis segundo a permutação `permuta`
|
| 166 |
+
/// (novo_papel[i] = papel_anterior[permuta[i]]). `redirecionar_todos`
|
| 167 |
+
/// troca Codificador ↔ Decodificador.
|
| 168 |
+
pub fn redirecionar(&mut self, permuta: &[usize]) {
|
| 169 |
+
let antigos = self.papeis.clone();
|
| 170 |
+
for (i, &p) in permuta.iter().enumerate() {
|
| 171 |
+
if p < antigos.len() {
|
| 172 |
+
self.papeis[i] = antigos[p];
|
| 173 |
+
}
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
pub fn redirecionar_todos(&mut self) {
|
| 178 |
+
for p in self.papeis.iter_mut() {
|
| 179 |
+
*p = match *p {
|
| 180 |
+
Papel::Codificador => Papel::Decodificador,
|
| 181 |
+
Papel::Decodificador => Papel::Codificador,
|
| 182 |
+
};
|
| 183 |
+
}
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
fn indices_do_papel(&self, papel: Papel) -> Vec<usize> {
|
| 187 |
+
self.papeis.iter().enumerate().filter(|(_, p)| **p == papel).map(|(i, _)| i).collect()
|
| 188 |
+
}
|
| 189 |
+
|
| 190 |
+
/// Passada por papel: atenção própria → roteamento top-k DENTRO do
|
| 191 |
+
/// subconjunto de especialistas do papel → capacidade → soma ponderada.
|
| 192 |
+
///
|
| 193 |
+
/// ⚠️ **v0.8.0 — bug v080-1 catalogado (r-33 §5):** esta versão
|
| 194 |
+
/// materializa os valores com `to_vec2`/`from_vec` no acúmulo, o que
|
| 195 |
+
/// SECCIONA o grafo de autograd — especialistas e roteador NÃO recebem
|
| 196 |
+
/// gradiente por esta saída (só a atenção própria treina). Mantida como
|
| 197 |
+
/// referência de VALORES (o teste M-R6 verifica a equivalência numérica
|
| 198 |
+
/// com a versão vetorizada); para TREINO use `fwd_vetorizado`.
|
| 199 |
+
pub fn fwd(&self, x: &Tensor, papel: Papel) -> Result<SaidaMoeR> {
|
| 200 |
+
let y = self.atencao.fwd(x)?; // (n, d) — atenção própria independente
|
| 201 |
+
let n = y.dim(0)?;
|
| 202 |
+
let d = y.dim(1)?;
|
| 203 |
+
let dev = y.device();
|
| 204 |
+
let do_papel = self.indices_do_papel(papel);
|
| 205 |
+
let e_local = do_papel.len();
|
| 206 |
+
let mut descartados = 0usize;
|
| 207 |
+
let mut saida = Tensor::zeros((n, d), candle_core::DType::F32, &dev)?;
|
| 208 |
+
let mut peso_total = Tensor::zeros((n, 1), candle_core::DType::F32, &dev)?;
|
| 209 |
+
// probs de roteamento só sobre especialistas do papel (mascarado)
|
| 210 |
+
let logits_todos = self.roteador.fwd(&y)?; // (n, E)
|
| 211 |
+
let mut probs_v: Vec<Vec<f32>> = logits_todos.to_vec2::<f32>()?;
|
| 212 |
+
for linha in probs_v.iter_mut() {
|
| 213 |
+
// máscara: -inf fora do papel
|
| 214 |
+
for (e, lv) in linha.iter_mut().enumerate() {
|
| 215 |
+
if !do_papel.contains(&e) {
|
| 216 |
+
*lv = f32::NEG_INFINITY;
|
| 217 |
+
}
|
| 218 |
+
}
|
| 219 |
+
}
|
| 220 |
+
// softmax manual por linha (com -inf → 0)
|
| 221 |
+
let probs: Vec<Vec<f32>> = probs_v
|
| 222 |
+
.iter()
|
| 223 |
+
.map(|linha| {
|
| 224 |
+
let m = linha.iter().copied().fold(f32::NEG_INFINITY, f32::max);
|
| 225 |
+
let exps: Vec<f32> = linha.iter().map(|v| (v - m).exp()).collect();
|
| 226 |
+
let s: f32 = exps.iter().sum();
|
| 227 |
+
if s <= 0.0 {
|
| 228 |
+
vec![0.0; linha.len()]
|
| 229 |
+
} else {
|
| 230 |
+
exps.iter().map(|e| e / s).collect()
|
| 231 |
+
}
|
| 232 |
+
})
|
| 233 |
+
.collect();
|
| 234 |
+
// seleção top-k com capacidade (código extraído — MESMA semântica
|
| 235 |
+
// de desempate da v0.6.0, compartilhada com fwd_vetorizado)
|
| 236 |
+
let (designacao, carga) = selecionar_com_capacidade(
|
| 237 |
+
&probs,
|
| 238 |
+
&do_papel,
|
| 239 |
+
n,
|
| 240 |
+
self.n_top,
|
| 241 |
+
self.capacidade,
|
| 242 |
+
self.especialistas.len(),
|
| 243 |
+
);
|
| 244 |
+
let usados: Vec<usize> = designacao.iter().map(|(ei, _)| *ei).collect();
|
| 245 |
+
for (ei, escolhidos) in designacao {
|
| 246 |
+
let idx: Vec<u32> = escolhidos.iter().map(|&i| i as u32).collect();
|
| 247 |
+
let n_e = idx.len();
|
| 248 |
+
let idx_t = Tensor::from_vec(idx, (n_e,), &dev)?;
|
| 249 |
+
let tile = y.index_select(&idx_t, 0)?;
|
| 250 |
+
let y_e = self.especialistas[ei].fwd(&tile)?;
|
| 251 |
+
let w: Vec<f32> = escolhidos.iter().map(|&i| probs[i][ei]).collect();
|
| 252 |
+
let w_t = Tensor::from_vec(w.clone(), (n_e, 1), &dev)?;
|
| 253 |
+
let contrib = y_e.broadcast_mul(&w_t)?;
|
| 254 |
+
let mut buf = saida.to_vec2::<f32>()?;
|
| 255 |
+
let contrib_v = contrib.to_vec2::<f32>()?;
|
| 256 |
+
for (kk, &i) in escolhidos.iter().enumerate() {
|
| 257 |
+
for j in 0..d {
|
| 258 |
+
buf[i][j] += contrib_v[kk][j];
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
saida = Tensor::from_vec(buf.into_iter().flatten().collect::<Vec<f32>>(), (n, d), &dev)?;
|
| 262 |
+
let mut pt = peso_total.to_vec2::<f32>()?;
|
| 263 |
+
for (kk, &i) in escolhidos.iter().enumerate() {
|
| 264 |
+
pt[i][0] += w[kk];
|
| 265 |
+
}
|
| 266 |
+
peso_total = Tensor::from_vec(pt.into_iter().flatten().collect::<Vec<f32>>(), (n, 1), &dev)?;
|
| 267 |
+
}
|
| 268 |
+
let pt_v = peso_total.to_vec2::<f32>()?;
|
| 269 |
+
for i in 0..n {
|
| 270 |
+
if pt_v[i][0] == 0.0 {
|
| 271 |
+
descartados += 1;
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
let denom = peso_total.clamp(1e-6, f64::INFINITY)?;
|
| 275 |
+
let saida = saida.broadcast_div(&denom)?;
|
| 276 |
+
// saída residual: y + roteamento (atenção já embutida em y)
|
| 277 |
+
let saida = (&y + &saida)?;
|
| 278 |
+
// perda auxiliar sobre o SUBCONJUNTO do papel (invariante global — M-R1)
|
| 279 |
+
let mut f = vec![0f32; e_local.max(1)];
|
| 280 |
+
let mut m = 0usize;
|
| 281 |
+
for i in 0..n {
|
| 282 |
+
let (mut melhor, mut mp) = (0usize, f32::NEG_INFINITY);
|
| 283 |
+
for (li, &g) in do_papel.iter().enumerate() {
|
| 284 |
+
if probs[i][g] > mp {
|
| 285 |
+
mp = probs[i][g];
|
| 286 |
+
melhor = li;
|
| 287 |
+
}
|
| 288 |
+
}
|
| 289 |
+
f[melhor] += 1.0;
|
| 290 |
+
m += 1;
|
| 291 |
+
}
|
| 292 |
+
if m > 0 && e_local > 0 {
|
| 293 |
+
for v in f.iter_mut() {
|
| 294 |
+
*v /= m as f32;
|
| 295 |
+
}
|
| 296 |
+
}
|
| 297 |
+
let f_t = Tensor::from_vec(f, (e_local.max(1),), &dev)?;
|
| 298 |
+
let p_media_local: Vec<f32> = (0..e_local)
|
| 299 |
+
.map(|li| {
|
| 300 |
+
let g = do_papel[li];
|
| 301 |
+
(0..n).map(|i| probs[i][g]).sum::<f32>() / n.max(1) as f32
|
| 302 |
+
})
|
| 303 |
+
.collect();
|
| 304 |
+
let p_t = Tensor::from_vec(p_media_local, (e_local.max(1),), &dev)?;
|
| 305 |
+
let perda_aux = ((&f_t * &p_t)?.sum_all()? * (e_local.max(1) as f64))?;
|
| 306 |
+
Ok(SaidaMoeR { saida, perda_aux, carga, descartados, usados })
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
/// **M-R6 (v0.8.0):** passada VETORIZADA — roteamento, acúmulo e perda
|
| 310 |
+
/// auxiliar em OPERAÇÕES DE TENSOR rastreadas (proposta Qwen-3.8 nº 4
|
| 311 |
+
/// IMPLEMENTADA de verdade: o esboço original terminava em
|
| 312 |
+
/// `unimplemented!()`; a fórmula da perda auxiliar `E·Σ f_e·p̄_e`
|
| 313 |
+
/// estava CORRETA — Switch Transformer — e é mantida).
|
| 314 |
+
///
|
| 315 |
+
/// **Correção do bug v080-1:** a acumulação usa `index_add` (op
|
| 316 |
+
/// rastreada pelo autograd do candle — `IndexAdd` em op.rs) em vez do
|
| 317 |
+
/// round-trip `to_vec2`/`from_vec`; os pesos de roteamento saem do
|
| 318 |
+
/// PRÓPRIO tensor `probs` (vivo até o roteador); a perda auxiliar usa
|
| 319 |
+
/// `p̄` do tensor vivo com `f` constante (padrão Switch: argmax é
|
| 320 |
+
/// não-diferenciável, a média de probs carrega o gradiente). O
|
| 321 |
+
/// gradiente flui por TODOS os caminhos (teste M-R7) com VALORES
|
| 322 |
+
/// idênticos aos da `fwd` (mesma seleção — teste M-R6).
|
| 323 |
+
pub fn fwd_vetorizado(&self, x: &Tensor, papel: Papel) -> Result<SaidaMoeR> {
|
| 324 |
+
let y = self.atencao.fwd(x)?; // (n, d)
|
| 325 |
+
let n = y.dim(0)?;
|
| 326 |
+
let d = y.dim(1)?;
|
| 327 |
+
let dev = y.device();
|
| 328 |
+
let e_total = self.especialistas.len();
|
| 329 |
+
let do_papel = self.indices_do_papel(papel);
|
| 330 |
+
let e_local = do_papel.len();
|
| 331 |
+
// 1) máscara do papel + softmax EM TENSOR — grafo VIVO até o roteador
|
| 332 |
+
let mut mascara_v = vec![f32::NEG_INFINITY; e_total];
|
| 333 |
+
for &e in &do_papel {
|
| 334 |
+
mascara_v[e] = 0.0;
|
| 335 |
+
}
|
| 336 |
+
let mascara = Tensor::from_vec(mascara_v, (1, e_total), &dev)?;
|
| 337 |
+
let logits = self.roteador.fwd(&y)?;
|
| 338 |
+
let mascarados = logits.broadcast_add(&mascara)?;
|
| 339 |
+
let probs = candle_nn::ops::softmax(&mascarados, candle_core::D::Minus1)?; // (n, E) VIVO
|
| 340 |
+
// 2) seleção discreta (única materialização — o roteamento duro é
|
| 341 |
+
// não-diferenciável por natureza; os PESOS ficam vivos no passo 3)
|
| 342 |
+
let probs_v = probs.to_vec2::<f32>()?;
|
| 343 |
+
let (designacao, carga) =
|
| 344 |
+
selecionar_com_capacidade(&probs_v, &do_papel, n, self.n_top, self.capacidade, e_total);
|
| 345 |
+
let usados: Vec<usize> = designacao.iter().map(|(ei, _)| *ei).collect();
|
| 346 |
+
// 3) acúmulo VIVO: index_add é rastreada — especialistas recebem
|
| 347 |
+
// gradiente pelas contribuições, e o roteador pelos pesos w
|
| 348 |
+
let mut saida = Tensor::zeros((n, d), candle_core::DType::F32, &dev)?;
|
| 349 |
+
let mut peso_total = Tensor::zeros((n, 1), candle_core::DType::F32, &dev)?;
|
| 350 |
+
for (ei, escolhidos) in &designacao {
|
| 351 |
+
let idx: Vec<u32> = escolhidos.iter().map(|&i| i as u32).collect();
|
| 352 |
+
let n_e = idx.len();
|
| 353 |
+
let idx_t = Tensor::from_vec(idx, (n_e,), &dev)?;
|
| 354 |
+
let tile = y.index_select(&idx_t, 0)?; // VIVO
|
| 355 |
+
let y_e = self.especialistas[*ei].fwd(&tile)?; // VIVO (gradiente!)
|
| 356 |
+
// w = probs[i][ei] VIVO: gradiente flui ao roteador
|
| 357 |
+
let linhas = probs.index_select(&idx_t, 0)?; // (n_e, E) VIVO
|
| 358 |
+
let w_t = linhas.narrow(1, *ei, 1)?.contiguous()?; // (n_e, 1) VIVO
|
| 359 |
+
let contrib = y_e.broadcast_mul(&w_t)?; // (n_e, d) VIVO
|
| 360 |
+
saida = saida.index_add(&idx_t, &contrib, 0)?; // VIVO (M-R6)
|
| 361 |
+
peso_total = peso_total.index_add(&idx_t, &w_t, 0)?; // VIVO
|
| 362 |
+
}
|
| 363 |
+
// 4) normalização idêntica à fwd (clamp 1e-6 nos descartados)
|
| 364 |
+
let pt_v = peso_total.to_vec2::<f32>()?; // estatística apenas
|
| 365 |
+
let mut descartados = 0usize;
|
| 366 |
+
for i in 0..n {
|
| 367 |
+
if pt_v[i][0] == 0.0 {
|
| 368 |
+
descartados += 1;
|
| 369 |
+
}
|
| 370 |
+
}
|
| 371 |
+
let denom = peso_total.clamp(1e-6, f64::INFINITY)?;
|
| 372 |
+
let saida = saida.broadcast_div(&denom)?;
|
| 373 |
+
let saida = (&y + &saida)?; // residual — como a fwd
|
| 374 |
+
// 5) perda auxiliar EM TENSOR (fórmula M-R1/Switch: e_local·Σ f_e·p̄_e)
|
| 375 |
+
let cols: Vec<u32> = do_papel.iter().map(|&e| e as u32).collect();
|
| 376 |
+
let cols_t = Tensor::from_vec(cols, (e_local.max(1),), &dev)?;
|
| 377 |
+
let probs_local = probs.index_select(&cols_t, 1)?; // (n, e_local) VIVO
|
| 378 |
+
let argmax_v = probs_local.argmax(candle_core::D::Minus1)?.to_vec1::<u32>()?;
|
| 379 |
+
let mut f = vec![0f32; e_local.max(1)];
|
| 380 |
+
for &a in &argmax_v {
|
| 381 |
+
f[a as usize] += 1.0;
|
| 382 |
+
}
|
| 383 |
+
if n > 0 {
|
| 384 |
+
for v in f.iter_mut() {
|
| 385 |
+
*v /= n as f32;
|
| 386 |
+
}
|
| 387 |
+
}
|
| 388 |
+
let f_t = Tensor::from_vec(f, (e_local.max(1),), &dev)?;
|
| 389 |
+
let p_barra = probs_local.mean_keepdim(0)?.flatten_all()?; // (e_local,) VIVO
|
| 390 |
+
let perda_aux = ((&f_t * &p_barra)?.sum_all()? * (e_local.max(1) as f64))?;
|
| 391 |
+
Ok(SaidaMoeR { saida, perda_aux, carga, descartados, usados })
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
/// **M-R3:** passadas Codificador e Decodificador em DUAS THREADS
|
| 395 |
+
/// (DAGs independentes — especialistas disjuntos por papel). Devolve
|
| 396 |
+
/// (enc, dec, tempo_paralelo_s, tempo_sequencial_s, speedup).
|
| 397 |
+
pub fn fwd_ambos_par(&self, x: &Tensor) -> Result<(SaidaMoeR, SaidaMoeR, f64, f64, f64)> {
|
| 398 |
+
use std::time::Instant;
|
| 399 |
+
// sequencial (medição)
|
| 400 |
+
let t0 = Instant::now();
|
| 401 |
+
let enc_seq = self.fwd(x, Papel::Codificador)?;
|
| 402 |
+
let dec_seq = self.fwd(x, Papel::Decodificador)?;
|
| 403 |
+
let t_seq = t0.elapsed().as_secs_f64();
|
| 404 |
+
// paralelo (2 threads com escopo — referências compartilhadas)
|
| 405 |
+
let t0 = Instant::now();
|
| 406 |
+
let (enc, dec) = {
|
| 407 |
+
let xref = &x;
|
| 408 |
+
let sref = &self;
|
| 409 |
+
std::thread::scope(|sc| {
|
| 410 |
+
let h1 = sc.spawn(move || sref.fwd(xref, Papel::Codificador));
|
| 411 |
+
let h2 = sc.spawn(move || sref.fwd(xref, Papel::Decodificador));
|
| 412 |
+
let r1 = h1.join().unwrap_or_else(|_| Err(candle_core::Error::Msg("thread enc".into())));
|
| 413 |
+
let r2 = h2.join().unwrap_or_else(|_| Err(candle_core::Error::Msg("thread dec".into())));
|
| 414 |
+
(r1, r2)
|
| 415 |
+
})
|
| 416 |
+
};
|
| 417 |
+
let (enc, dec) = (enc?, dec?);
|
| 418 |
+
let t_par = t0.elapsed().as_secs_f64();
|
| 419 |
+
// determinismo: paralelo == sequencial (mesmas entradas, especialistas disjuntos)
|
| 420 |
+
let dif = (&enc.saida - &enc_seq.saida)?.sqr()?.sum_all()?.to_scalar::<f32>()? as f64
|
| 421 |
+
+ (&dec.saida - &dec_seq.saida)?.sqr()?.sum_all()?.to_scalar::<f32>()? as f64;
|
| 422 |
+
assert!(dif < 1e-9, "paralelo deve ser bit-a-bit igual ao sequencial (dif={dif})");
|
| 423 |
+
let speedup = t_seq / t_par.max(1e-12);
|
| 424 |
+
Ok((enc, dec, t_par, t_seq, speedup))
|
| 425 |
+
}
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
// ---------------------------------------------------------------------------
|
| 429 |
+
// M-R5 — alocação water-filling por QE
|
| 430 |
+
// ---------------------------------------------------------------------------
|
| 431 |
+
|
| 432 |
+
/// Seleção top-k com capacidade por especialista (v0.8.0 — extraída
|
| 433 |
+
/// VERBATIM do loop da `fwd` v0.6.0: mesma ordem de candidatos, mesmos
|
| 434 |
+
/// desempates estáveis de `sort_by`). Compartilhada por `fwd` e
|
| 435 |
+
/// `fwd_vetorizado` ⇒ as duas atribuem EXATAMENTE os mesmos tokens aos
|
| 436 |
+
/// mesmos especialistas (base do teste M-R6 — equivalência de valores).
|
| 437 |
+
/// Devolve (designação por especialista, carga global por especialista).
|
| 438 |
+
fn selecionar_com_capacidade(
|
| 439 |
+
probs: &[Vec<f32>],
|
| 440 |
+
do_papel: &[usize],
|
| 441 |
+
n: usize,
|
| 442 |
+
n_top: usize,
|
| 443 |
+
capacidade: usize,
|
| 444 |
+
n_especialistas: usize,
|
| 445 |
+
) -> (Vec<(usize, Vec<usize>)>, Vec<usize>) {
|
| 446 |
+
let mut carga = vec![0usize; n_especialistas];
|
| 447 |
+
let mut designacao: Vec<(usize, Vec<usize>)> = Vec::new();
|
| 448 |
+
for &ei in do_papel.iter() {
|
| 449 |
+
// candidatos ordenados por prob do especialista ei (desc)
|
| 450 |
+
let mut cand: Vec<(usize, f32)> = (0..n).map(|i| (i, probs[i][ei])).collect();
|
| 451 |
+
cand.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
| 452 |
+
let mut escolhidos: Vec<usize> = Vec::new();
|
| 453 |
+
for (i, _) in cand {
|
| 454 |
+
if carga[ei] >= capacidade {
|
| 455 |
+
break;
|
| 456 |
+
}
|
| 457 |
+
// ei está no top-k(papel) do token i?
|
| 458 |
+
let mut ordem: Vec<(f32, usize)> =
|
| 459 |
+
do_papel.iter().map(|&g| (probs[i][g], g)).collect();
|
| 460 |
+
ordem.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
|
| 461 |
+
let top: Vec<usize> = ordem.iter().rev().take(n_top).map(|(_, g)| *g).collect();
|
| 462 |
+
if top.contains(&ei) {
|
| 463 |
+
escolhidos.push(i);
|
| 464 |
+
carga[ei] += 1;
|
| 465 |
+
}
|
| 466 |
+
}
|
| 467 |
+
if escolhidos.is_empty() {
|
| 468 |
+
continue;
|
| 469 |
+
}
|
| 470 |
+
designacao.push((ei, escolhidos));
|
| 471 |
+
}
|
| 472 |
+
(designacao, carga)
|
| 473 |
+
}
|
| 474 |
+
|
| 475 |
+
/// Aloca `capacidade_total` especialistas entre células com QE `qes`
|
| 476 |
+
/// proporcionalmente ao QE (Cauchy–Schwarz: minimiza Σ q_i²/c_i).
|
| 477 |
+
/// Devolve c_i ≥ 1 com Σ c_i = capacidade_total (se capacidade ≥ células).
|
| 478 |
+
pub fn alocar_por_qe(qes: &[f64], capacidade_total: usize) -> Vec<usize> {
|
| 479 |
+
let m = qes.len();
|
| 480 |
+
if m == 0 {
|
| 481 |
+
return Vec::new();
|
| 482 |
+
}
|
| 483 |
+
let soma: f64 = qes.iter().sum::<f64>().max(1e-12);
|
| 484 |
+
let mut c: Vec<usize> = qes
|
| 485 |
+
.iter()
|
| 486 |
+
.map(|q| ((q / soma) * capacidade_total as f64).round() as usize)
|
| 487 |
+
.collect();
|
| 488 |
+
// garante mínimo 1 e corrige a soma por maior resíduo
|
| 489 |
+
for ci in c.iter_mut() {
|
| 490 |
+
*ci = (*ci).max(1);
|
| 491 |
+
}
|
| 492 |
+
let mut total: usize = c.iter().sum();
|
| 493 |
+
while total > capacidade_total.max(m) {
|
| 494 |
+
// remove da maior alocação
|
| 495 |
+
let (mut imax, mut vmax) = (0usize, 0usize);
|
| 496 |
+
for (i, &v) in c.iter().enumerate() {
|
| 497 |
+
if v > vmax {
|
| 498 |
+
vmax = v;
|
| 499 |
+
imax = i;
|
| 500 |
+
}
|
| 501 |
+
}
|
| 502 |
+
if c[imax] > 1 {
|
| 503 |
+
c[imax] -= 1;
|
| 504 |
+
}
|
| 505 |
+
total -= 1;
|
| 506 |
+
}
|
| 507 |
+
while total < capacidade_total {
|
| 508 |
+
let (mut imax, mut vmax) = (0usize, 0.0f64);
|
| 509 |
+
for (i, (&v, &q)) in c.iter().zip(qes.iter()).enumerate() {
|
| 510 |
+
let res = q / (v as f64);
|
| 511 |
+
if res > vmax {
|
| 512 |
+
vmax = res;
|
| 513 |
+
imax = i;
|
| 514 |
+
}
|
| 515 |
+
}
|
| 516 |
+
c[imax] += 1;
|
| 517 |
+
total += 1;
|
| 518 |
+
}
|
| 519 |
+
c
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
// ---------------------------------------------------------------------------
|
| 523 |
+
// Aplicação JEPA — codificador/preditor redirecionáveis com MoE
|
| 524 |
+
// ---------------------------------------------------------------------------
|
| 525 |
+
|
| 526 |
+
pub struct RelatorioJepaMoe {
|
| 527 |
+
pub modo: &'static str,
|
| 528 |
+
pub perda_pred_inicial: f64,
|
| 529 |
+
pub perda_pred_final: f64,
|
| 530 |
+
pub perda_vc_final: f64,
|
| 531 |
+
pub std_emb: f64,
|
| 532 |
+
pub carga_max: usize,
|
| 533 |
+
pub descartados: usize,
|
| 534 |
+
}
|
| 535 |
+
|
| 536 |
+
/// Treino JEPA compacto com três braços (ablação honesta):
|
| 537 |
+
/// - `"pequena"`: codificador/preditor MLP simples (baseline);
|
| 538 |
+
/// - `"densa"`: MLP com o TOTAL de parâmetros dos especialistas (capacidade
|
| 539 |
+
/// equiparada, SEM roteamento — isola o ganho do roteamento);
|
| 540 |
+
/// - `"moe"`: `MoeRedirecionavel` nos dois caminhos (Codificador no
|
| 541 |
+
/// codificador, Decodificador no preditor) com atenção própria.
|
| 542 |
+
pub fn treinar_jepa_com_moe(
|
| 543 |
+
patches: &Tensor,
|
| 544 |
+
modo: &'static str,
|
| 545 |
+
passos: usize,
|
| 546 |
+
frac_mascara: f64,
|
| 547 |
+
semente: u64,
|
| 548 |
+
) -> Result<RelatorioJepaMoe> {
|
| 549 |
+
let dev = patches.device();
|
| 550 |
+
let n = patches.dim(0)?;
|
| 551 |
+
let d = patches.dim(1)?;
|
| 552 |
+
let mut rng = RngDeterministico::novo(semente);
|
| 553 |
+
// alvo = patches originais (detach); contexto = patches mascarados
|
| 554 |
+
let alvo = patches.detach();
|
| 555 |
+
let contexto = {
|
| 556 |
+
let mut v = patches.flatten_all()?.to_vec1::<f32>()?;
|
| 557 |
+
for x in v.iter_mut() {
|
| 558 |
+
if rng.uniforme() < frac_mascara {
|
| 559 |
+
*x = 0.0;
|
| 560 |
+
}
|
| 561 |
+
}
|
| 562 |
+
Tensor::from_vec(v, (n, d), &dev)?
|
| 563 |
+
};
|
| 564 |
+
let e = 4usize; // especialistas por papel no braço moe
|
| 565 |
+
let n_top = 2;
|
| 566 |
+
let capacidade = n;
|
| 567 |
+
|
| 568 |
+
// braços
|
| 569 |
+
enum Braco {
|
| 570 |
+
Pequena(MlpJepa, MlpJepa),
|
| 571 |
+
Densa(MlpJepa, MlpJepa),
|
| 572 |
+
Moe(MoeRedirecionavel),
|
| 573 |
+
}
|
| 574 |
+
let braco = match modo {
|
| 575 |
+
"pequena" => Braco::Pequena(
|
| 576 |
+
MlpJepa::novo(d, 2 * d, d, &dev)?,
|
| 577 |
+
MlpJepa::novo(d, 2 * d, d, &dev)?,
|
| 578 |
+
),
|
| 579 |
+
"densa" => {
|
| 580 |
+
// capacidade equiparada: oculta = soma das ocultas dos especialistas
|
| 581 |
+
let d_oculta = 2 * d * e;
|
| 582 |
+
Braco::Densa(
|
| 583 |
+
MlpJepa::novo(d, d_oculta, d, &dev)?,
|
| 584 |
+
MlpJepa::novo(d, d_oculta, d, &dev)?,
|
| 585 |
+
)
|
| 586 |
+
}
|
| 587 |
+
_ => Braco::Moe(MoeRedirecionavel::novo(d, 2 * e, n_top, capacidade, &dev)?),
|
| 588 |
+
};
|
| 589 |
+
|
| 590 |
+
let perda_de = |b: &Braco, x: &Tensor| -> Result<(Tensor, Tensor, usize)> {
|
| 591 |
+
match b {
|
| 592 |
+
Braco::Pequena(e1, p1) => {
|
| 593 |
+
let z = e1.fwd(x)?;
|
| 594 |
+
let yh = p1.fwd(&z)?;
|
| 595 |
+
let dif = (&yh - &alvo)?;
|
| 596 |
+
Ok(((dif.sqr()?.sum_all()? / (n as f64 * d as f64))?,
|
| 597 |
+
z.sqr()?.mean_all()?, 0))
|
| 598 |
+
}
|
| 599 |
+
Braco::Densa(e1, p1) => {
|
| 600 |
+
let z = e1.fwd(x)?;
|
| 601 |
+
let yh = p1.fwd(&z)?;
|
| 602 |
+
let dif = (&yh - &alvo)?;
|
| 603 |
+
Ok(((dif.sqr()?.sum_all()? / (n as f64 * d as f64))?,
|
| 604 |
+
z.sqr()?.mean_all()?, 0))
|
| 605 |
+
}
|
| 606 |
+
Braco::Moe(m) => {
|
| 607 |
+
// v0.8.0 (bug v080-1): fwd_vetorizado — gradiente flui aos
|
| 608 |
+
// especialistas e ao roteador (a fwd seccionava o grafo)
|
| 609 |
+
let s_enc = m.fwd_vetorizado(x, Papel::Codificador)?;
|
| 610 |
+
let s_dec = m.fwd_vetorizado(&s_enc.saida, Papel::Decodificador)?;
|
| 611 |
+
let dif = (&s_dec.saida - &alvo)?;
|
| 612 |
+
let lp = (dif.sqr()?.sum_all()? / (n as f64 * d as f64))?;
|
| 613 |
+
// perda total = preditiva + aux balanceamento (ambos papéis)
|
| 614 |
+
let lt = (lp.affine(1.0, 0.0)? + s_enc.perda_aux.affine(m.lambda_aux, 0.0)?
|
| 615 |
+
+ s_dec.perda_aux.affine(m.lambda_aux, 0.0)?)?;
|
| 616 |
+
let carga_max = s_enc.carga.iter().copied().fold(0, usize::max)
|
| 617 |
+
.max(s_dec.carga.iter().copied().fold(0, usize::max));
|
| 618 |
+
Ok((lt, lp, carga_max))
|
| 619 |
+
}
|
| 620 |
+
}
|
| 621 |
+
};
|
| 622 |
+
|
| 623 |
+
let (mut opt, vars): (AdamWManual, Vec<Var>) = match &braco {
|
| 624 |
+
Braco::Pequena(a, b) => {
|
| 625 |
+
let v = [a.vars(), b.vars()].concat();
|
| 626 |
+
(AdamWManual::novo(v.clone(), 3e-3, 0.0), v)
|
| 627 |
+
}
|
| 628 |
+
Braco::Densa(a, b) => {
|
| 629 |
+
let v = [a.vars(), b.vars()].concat();
|
| 630 |
+
(AdamWManual::novo(v.clone(), 3e-3, 0.0), v)
|
| 631 |
+
}
|
| 632 |
+
Braco::Moe(m) => (AdamWManual::novo(m.vars(), 3e-3, 0.0), m.vars()),
|
| 633 |
+
};
|
| 634 |
+
// atenção própria: gate nasce 0; sobe 1 passo em 8 (aquecimento honesto
|
| 635 |
+
// do mecanismo — T-P5 análogo: neutralidade no nascimento, gradiente flui)
|
| 636 |
+
if let Braco::Moe(m) = &braco {
|
| 637 |
+
let g0 = m.atencao.gate.as_tensor().to_scalar::<f32>()? as f64;
|
| 638 |
+
let _ = g0;
|
| 639 |
+
}
|
| 640 |
+
|
| 641 |
+
let (l0, _, _) = perda_de(&braco, &contexto)?;
|
| 642 |
+
let perda_inicial = l0.to_scalar::<f32>()? as f64;
|
| 643 |
+
let mut perda_final = perda_inicial;
|
| 644 |
+
let mut vc_final = 0.0;
|
| 645 |
+
let mut carga_max = 0usize;
|
| 646 |
+
let mut descartados = 0usize;
|
| 647 |
+
let mut std_emb = 0.0;
|
| 648 |
+
for passo in 0..passos {
|
| 649 |
+
let (lt, lp, cm) = perda_de(&braco, &contexto)?;
|
| 650 |
+
if let Braco::Moe(m) = &braco {
|
| 651 |
+
// abre o gate gradualmente (0 → 1 em 25% dos passos)
|
| 652 |
+
let alvo_g = ((passo as f64 / (passos as f64 / 4.0).max(1.0)).min(1.0)) as f32;
|
| 653 |
+
let g_atual = m.atencao.gate.as_tensor().to_scalar::<f32>()? as f64;
|
| 654 |
+
if (alvo_g as f64 - g_atual).abs() > 1e-9 {
|
| 655 |
+
m.atencao.gate.set(&Tensor::new(alvo_g, &dev)?)?;
|
| 656 |
+
}
|
| 657 |
+
}
|
| 658 |
+
let grads = crate::treino::grads_achatados(<, &vars)?;
|
| 659 |
+
AdamWManual::clipar(&mut grads.clone(), 1.0);
|
| 660 |
+
let mut g2 = grads;
|
| 661 |
+
AdamWManual::clipar(&mut g2, 5.0);
|
| 662 |
+
opt.passo(g2);
|
| 663 |
+
perda_final = lp.to_scalar::<f32>()? as f64;
|
| 664 |
+
carga_max = cm;
|
| 665 |
+
// VC / std do embedding (para o relatório)
|
| 666 |
+
let (_, vc, _) = perda_de(&braco, &contexto)?;
|
| 667 |
+
vc_final = vc.to_scalar::<f32>()? as f64;
|
| 668 |
+
std_emb = {
|
| 669 |
+
let z = match &braco {
|
| 670 |
+
Braco::Pequena(a, _) => a.fwd(&contexto)?,
|
| 671 |
+
Braco::Densa(a, _) => a.fwd(&contexto)?,
|
| 672 |
+
Braco::Moe(m) => m.fwd(&contexto, Papel::Codificador)?.saida,
|
| 673 |
+
};
|
| 674 |
+
z.flatten_all()?.to_vec1::<f32>()?.iter().map(|x| *x as f64).collect::<Vec<f64>>()
|
| 675 |
+
.chunks(n).map(|c| {
|
| 676 |
+
let m = c.iter().sum::<f64>() / c.len() as f64;
|
| 677 |
+
(c.iter().map(|x| (x - m).powi(2)).sum::<f64>() / c.len() as f64).sqrt()
|
| 678 |
+
}).sum::<f64>() / n as f64
|
| 679 |
+
};
|
| 680 |
+
}
|
| 681 |
+
if let Braco::Moe(m) = &braco {
|
| 682 |
+
let s = m.fwd_vetorizado(&contexto, Papel::Codificador)?;
|
| 683 |
+
descartados = s.descartados;
|
| 684 |
+
}
|
| 685 |
+
Ok(RelatorioJepaMoe {
|
| 686 |
+
modo,
|
| 687 |
+
perda_pred_inicial: perda_inicial,
|
| 688 |
+
perda_pred_final: perda_final,
|
| 689 |
+
perda_vc_final: vc_final,
|
| 690 |
+
std_emb,
|
| 691 |
+
carga_max,
|
| 692 |
+
descartados,
|
| 693 |
+
})
|
| 694 |
+
}
|
| 695 |
+
|
| 696 |
+
// ---------------------------------------------------------------------------
|
| 697 |
+
// Aplicação SOM — refinamento residual com alocação por QE (M-R5)
|
| 698 |
+
// ---------------------------------------------------------------------------
|
| 699 |
+
|
| 700 |
+
pub struct RelatorioSomMoe {
|
| 701 |
+
pub qe_antes: f64,
|
| 702 |
+
pub qe_depois: f64,
|
| 703 |
+
pub qe_alocacao_uniforme: f64,
|
| 704 |
+
pub carga_por_celula: Vec<usize>,
|
| 705 |
+
pub tempo_treino_s: f64,
|
| 706 |
+
/// qualidade da DECODIFICAÇÃO após redirecionamento (mse w_bmu → x).
|
| 707 |
+
pub mse_decodificador: f64,
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
/// Refina um SOM 4D treinado com um MoE redirecionável:
|
| 711 |
+
/// 1. aloca especialistas por célula ∝ QE (water-filling — M-R5);
|
| 712 |
+
/// 2. treina o papel Codificador como corretor residual
|
| 713 |
+
/// `x̂ = w_bmu(x) + E(x)`;
|
| 714 |
+
/// 3. mede QE′; repete com alocação uniforme (ablação do water-filling);
|
| 715 |
+
/// 4. REDIRECIONA os especialistas para Decodificador e treina
|
| 716 |
+
/// `x̂ = E(w_bmu)` (reconstrução a partir do código) — mede o MSE.
|
| 717 |
+
pub fn refinar_som_com_moe(
|
| 718 |
+
dados: &Tensor,
|
| 719 |
+
centroides: &[Vec<f64>],
|
| 720 |
+
passos: usize,
|
| 721 |
+
semente: u64,
|
| 722 |
+
) -> Result<RelatorioSomMoe> {
|
| 723 |
+
use std::time::Instant;
|
| 724 |
+
let t0 = Instant::now();
|
| 725 |
+
let dev = dados.device();
|
| 726 |
+
let n = dados.dim(0)?;
|
| 727 |
+
let d = dados.dim(1)?;
|
| 728 |
+
let m = centroides.len();
|
| 729 |
+
// BMU + QE por célula (tolera F32/F64)
|
| 730 |
+
let dados_v: Vec<Vec<f64>> = match dados.to_vec2::<f64>() {
|
| 731 |
+
Ok(v) => v,
|
| 732 |
+
Err(_) => dados
|
| 733 |
+
.to_vec2::<f32>()?
|
| 734 |
+
.iter()
|
| 735 |
+
.map(|l| l.iter().map(|x| *x as f64).collect())
|
| 736 |
+
.collect(),
|
| 737 |
+
};
|
| 738 |
+
let bmu: Vec<usize> = dados_v
|
| 739 |
+
.iter()
|
| 740 |
+
.map(|x| {
|
| 741 |
+
centroides
|
| 742 |
+
.iter()
|
| 743 |
+
.enumerate()
|
| 744 |
+
.min_by(|(i, a), (j, b)| {
|
| 745 |
+
let da: f64 = a.iter().zip(x).map(|(w, v)| (w - v).powi(2)).sum();
|
| 746 |
+
let db: f64 = b.iter().zip(x).map(|(w, v)| (w - v).powi(2)).sum();
|
| 747 |
+
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal).then(i.cmp(j))
|
| 748 |
+
})
|
| 749 |
+
.map(|(i, _)| i)
|
| 750 |
+
.unwrap_or(0)
|
| 751 |
+
})
|
| 752 |
+
.collect();
|
| 753 |
+
let mut qe_cel = vec![0f64; m];
|
| 754 |
+
let mut cont_cel = vec![0usize; m];
|
| 755 |
+
for (i, b) in bmu.iter().enumerate() {
|
| 756 |
+
let dist: f64 = centroides[*b].iter().zip(&dados_v[i]).map(|(w, v)| (w - v).powi(2)).sum();
|
| 757 |
+
qe_cel[*b] += dist.sqrt();
|
| 758 |
+
cont_cel[*b] += 1;
|
| 759 |
+
}
|
| 760 |
+
for i in 0..m {
|
| 761 |
+
if cont_cel[i] > 0 {
|
| 762 |
+
qe_cel[i] /= cont_cel[i] as f64;
|
| 763 |
+
}
|
| 764 |
+
}
|
| 765 |
+
let qe_antes: f64 = dados_v
|
| 766 |
+
.iter()
|
| 767 |
+
.enumerate()
|
| 768 |
+
.map(|(i, b)| {
|
| 769 |
+
let w = ¢roides[bmu[i]];
|
| 770 |
+
(w.iter().zip(b).map(|(a, v)| (a - v).powi(2)).sum::<f64>()).sqrt()
|
| 771 |
+
})
|
| 772 |
+
.sum::<f64>()
|
| 773 |
+
/ n as f64;
|
| 774 |
+
|
| 775 |
+
// ---------- braço water-filling ----------
|
| 776 |
+
// capacidade = m células + 8 especialistas EXTRA distribuídos ∝ QE
|
| 777 |
+
// (com C < m o water-filling degenera em uniforme — mínimo 1 por célula)
|
| 778 |
+
let capacidade_total = m + 8;
|
| 779 |
+
let aloca_wf = alocar_por_qe(&qe_cel, capacidade_total);
|
| 780 |
+
let qe_wf = treinar_corretor(dados, centroides, &bmu, &aloca_wf, passos, semente, &dev)?;
|
| 781 |
+
// ---------- braço uniforme (ablação: MESMO total e MESMA semente —
|
| 782 |
+
// única diferença é a alocação; round-robin soma exatamente C) ----------
|
| 783 |
+
let aloca_un = {
|
| 784 |
+
let mut a = vec![1usize; m];
|
| 785 |
+
let mut i = 0usize;
|
| 786 |
+
while a.iter().sum::<usize>() < capacidade_total {
|
| 787 |
+
a[i % m] += 1;
|
| 788 |
+
i += 1;
|
| 789 |
+
}
|
| 790 |
+
a
|
| 791 |
+
};
|
| 792 |
+
let qe_un = treinar_corretor(dados, centroides, &bmu, &aloca_un, passos, semente, &dev)?;
|
| 793 |
+
|
| 794 |
+
// ---------- redirecionamento → decodificador ----------
|
| 795 |
+
// mesma pool de especialistas, agora aprendendo w_bmu → x
|
| 796 |
+
let mse_dec = treinar_decodificador(dados, centroides, &bmu, &aloca_wf, passos, semente + 2, &dev)?;
|
| 797 |
+
|
| 798 |
+
Ok(RelatorioSomMoe {
|
| 799 |
+
qe_antes,
|
| 800 |
+
qe_depois: qe_wf,
|
| 801 |
+
qe_alocacao_uniforme: qe_un,
|
| 802 |
+
carga_por_celula: aloca_wf,
|
| 803 |
+
tempo_treino_s: t0.elapsed().as_secs_f64(),
|
| 804 |
+
mse_decodificador: mse_dec,
|
| 805 |
+
})
|
| 806 |
+
}
|
| 807 |
+
|
| 808 |
+
/// Corretor residual: x̂ = w_bmu + E_{célula}(x). Treina com AdamW.
|
| 809 |
+
fn treinar_corretor(
|
| 810 |
+
dados: &Tensor,
|
| 811 |
+
centroides: &[Vec<f64>],
|
| 812 |
+
bmu: &[usize],
|
| 813 |
+
alocacao: &[usize],
|
| 814 |
+
passos: usize,
|
| 815 |
+
semente: u64,
|
| 816 |
+
dev: &Device,
|
| 817 |
+
) -> Result<f64> {
|
| 818 |
+
let n = dados.dim(0)?;
|
| 819 |
+
let d = dados.dim(1)?;
|
| 820 |
+
// um "especialista" = MlpJepa por CÉLULA (capacidade alocada = nº de
|
| 821 |
+
// especialistas extra treinados nessa célula, somados em ensemble)
|
| 822 |
+
let n_esp_tot: usize = alocacao.iter().sum();
|
| 823 |
+
let mut ensembles: Vec<Vec<MlpJepa>> = Vec::with_capacity(centroides.len());
|
| 824 |
+
for c in alocacao {
|
| 825 |
+
let mut v = Vec::new();
|
| 826 |
+
for _ in 0..(*c).max(1) {
|
| 827 |
+
v.push(MlpJepa::novo(d, 4 * d, d, dev)?);
|
| 828 |
+
}
|
| 829 |
+
ensembles.push(v);
|
| 830 |
+
}
|
| 831 |
+
let mut vars: Vec<Var> = Vec::new();
|
| 832 |
+
for ens in &ensembles {
|
| 833 |
+
for e in ens {
|
| 834 |
+
vars.extend(e.vars());
|
| 835 |
+
}
|
| 836 |
+
}
|
| 837 |
+
let mut opt = AdamWManual::novo(vars.clone(), 3e-3, 0.0);
|
| 838 |
+
// tensores de apoio
|
| 839 |
+
let w_bmu = {
|
| 840 |
+
let v: Vec<f32> = (0..n).flat_map(|i| centroides[bmu[i]].iter().map(|x| *x as f32).collect::<Vec<f32>>()).collect();
|
| 841 |
+
Tensor::from_vec(v, (n, d), dev)?
|
| 842 |
+
};
|
| 843 |
+
let alvo_res = (dados - &w_bmu)?;
|
| 844 |
+
for _ in 0..passos {
|
| 845 |
+
let mut soma = Tensor::zeros((), candle_core::DType::F32, dev)?;
|
| 846 |
+
for (i, ens) in ensembles.iter().enumerate() {
|
| 847 |
+
// tokens da célula i
|
| 848 |
+
let idx: Vec<u32> = (0..n).filter(|&k| bmu[k] == i).map(|k| k as u32).collect();
|
| 849 |
+
if idx.is_empty() {
|
| 850 |
+
continue;
|
| 851 |
+
}
|
| 852 |
+
let m_idx = idx.len();
|
| 853 |
+
let it = Tensor::from_vec(idx, (m_idx,), dev)?;
|
| 854 |
+
let x_c = dados.index_select(&it, 0)?;
|
| 855 |
+
let r_c = alvo_res.index_select(&it, 0)?;
|
| 856 |
+
// ensemble: média das saídas
|
| 857 |
+
let mut y = Tensor::zeros((m_idx, d), candle_core::DType::F32, dev)?;
|
| 858 |
+
for e in ens {
|
| 859 |
+
y = (y.affine(1.0, 0.0)? + e.fwd(&x_c)?.affine(1.0, 0.0)?)?;
|
| 860 |
+
}
|
| 861 |
+
let k = ens.len() as f64;
|
| 862 |
+
let yh = y.affine(1.0 / k, 0.0)?;
|
| 863 |
+
let dif = (&yh - &r_c)?;
|
| 864 |
+
soma = (soma.affine(1.0, 0.0)? + dif.sqr()?.sum_all()?.affine(1.0 / (n as f64 * d as f64), 0.0)?)?;
|
| 865 |
+
}
|
| 866 |
+
let _ = n_esp_tot;
|
| 867 |
+
let grads = crate::treino::grads_achatados(&soma, &vars)?;
|
| 868 |
+
let mut g = grads;
|
| 869 |
+
AdamWManual::clipar(&mut g, 5.0);
|
| 870 |
+
opt.passo(g);
|
| 871 |
+
}
|
| 872 |
+
// QE refinado
|
| 873 |
+
let mut qe = 0f64;
|
| 874 |
+
for (i, ens) in ensembles.iter().enumerate() {
|
| 875 |
+
let idx: Vec<u32> = (0..n).filter(|&k| bmu[k] == i).map(|k| k as u32).collect();
|
| 876 |
+
if idx.is_empty() {
|
| 877 |
+
continue;
|
| 878 |
+
}
|
| 879 |
+
let m_idx = idx.len();
|
| 880 |
+
let it = Tensor::from_vec(idx, (m_idx,), dev)?;
|
| 881 |
+
let x_c = dados.index_select(&it, 0)?;
|
| 882 |
+
let mut y = Tensor::zeros((m_idx, d), candle_core::DType::F32, dev)?;
|
| 883 |
+
for e in ens {
|
| 884 |
+
y = (y.affine(1.0, 0.0)? + e.fwd(&x_c)?.affine(1.0, 0.0)?)?;
|
| 885 |
+
}
|
| 886 |
+
let k = ens.len() as f64;
|
| 887 |
+
let yh = y.affine(1.0 / k, 0.0)?;
|
| 888 |
+
let xhat = (w_bmu.index_select(&it, 0)? + &yh)?;
|
| 889 |
+
let dif = (&x_c - &xhat)?;
|
| 890 |
+
qe += dif.sqr()?.sum_all()?.to_scalar::<f32>()? as f64;
|
| 891 |
+
}
|
| 892 |
+
Ok((qe / (n as f64 * d as f64)).sqrt() * (d as f64).sqrt()) // QE médio por ponto (norma L2)
|
| 893 |
+
}
|
| 894 |
+
|
| 895 |
+
/// Decodificador (pós-redirecionamento): x̂ = E(w_bmu) — reconstrução
|
| 896 |
+
/// a partir apenas do código (centróide). Mede o MSE final.
|
| 897 |
+
fn treinar_decodificador(
|
| 898 |
+
dados: &Tensor,
|
| 899 |
+
centroides: &[Vec<f64>],
|
| 900 |
+
bmu: &[usize],
|
| 901 |
+
alocacao: &[usize],
|
| 902 |
+
passos: usize,
|
| 903 |
+
semente: u64,
|
| 904 |
+
dev: &Device,
|
| 905 |
+
) -> Result<f64> {
|
| 906 |
+
let _ = semente;
|
| 907 |
+
let n = dados.dim(0)?;
|
| 908 |
+
let d = dados.dim(1)?;
|
| 909 |
+
let mut ensembles: Vec<Vec<MlpJepa>> = Vec::with_capacity(centroides.len());
|
| 910 |
+
for c in alocacao {
|
| 911 |
+
let mut v = Vec::new();
|
| 912 |
+
for _ in 0..(*c).max(1) {
|
| 913 |
+
v.push(MlpJepa::novo(d, 4 * d, d, dev)?);
|
| 914 |
+
}
|
| 915 |
+
ensembles.push(v);
|
| 916 |
+
}
|
| 917 |
+
let mut vars: Vec<Var> = Vec::new();
|
| 918 |
+
for ens in &ensembles {
|
| 919 |
+
for e in ens {
|
| 920 |
+
vars.extend(e.vars());
|
| 921 |
+
}
|
| 922 |
+
}
|
| 923 |
+
let mut opt = AdamWManual::novo(vars.clone(), 3e-3, 0.0);
|
| 924 |
+
let w_bmu = {
|
| 925 |
+
let v: Vec<f32> = (0..n).flat_map(|i| centroides[bmu[i]].iter().map(|x| *x as f32).collect::<Vec<f32>>()).collect();
|
| 926 |
+
Tensor::from_vec(v, (n, d), dev)?
|
| 927 |
+
};
|
| 928 |
+
for _ in 0..passos {
|
| 929 |
+
let mut soma = Tensor::zeros((), candle_core::DType::F32, dev)?;
|
| 930 |
+
for (i, ens) in ensembles.iter().enumerate() {
|
| 931 |
+
let idx: Vec<u32> = (0..n).filter(|&k| bmu[k] == i).map(|k| k as u32).collect();
|
| 932 |
+
if idx.is_empty() {
|
| 933 |
+
continue;
|
| 934 |
+
}
|
| 935 |
+
let m_idx = idx.len();
|
| 936 |
+
let it = Tensor::from_vec(idx, (m_idx,), dev)?;
|
| 937 |
+
let w_c = w_bmu.index_select(&it, 0)?;
|
| 938 |
+
let x_c = dados.index_select(&it, 0)?;
|
| 939 |
+
let mut y = Tensor::zeros((m_idx, d), candle_core::DType::F32, dev)?;
|
| 940 |
+
for e in ens {
|
| 941 |
+
y = (y.affine(1.0, 0.0)? + e.fwd(&w_c)?.affine(1.0, 0.0)?)?;
|
| 942 |
+
}
|
| 943 |
+
let k = ens.len() as f64;
|
| 944 |
+
let yh = y.affine(1.0 / k, 0.0)?;
|
| 945 |
+
let dif = (&yh - &x_c)?;
|
| 946 |
+
soma = (soma.affine(1.0, 0.0)? + dif.sqr()?.sum_all()?.affine(1.0 / (n as f64 * d as f64), 0.0)?)?;
|
| 947 |
+
}
|
| 948 |
+
let grads = crate::treino::grads_achatados(&soma, &vars)?;
|
| 949 |
+
let mut g = grads;
|
| 950 |
+
AdamWManual::clipar(&mut g, 5.0);
|
| 951 |
+
opt.passo(g);
|
| 952 |
+
}
|
| 953 |
+
// MSE final do decodificador
|
| 954 |
+
let mut mse = 0f64;
|
| 955 |
+
for (i, ens) in ensembles.iter().enumerate() {
|
| 956 |
+
let idx: Vec<u32> = (0..n).filter(|&k| bmu[k] == i).map(|k| k as u32).collect();
|
| 957 |
+
if idx.is_empty() {
|
| 958 |
+
continue;
|
| 959 |
+
}
|
| 960 |
+
let m_idx = idx.len();
|
| 961 |
+
let it = Tensor::from_vec(idx, (m_idx,), dev)?;
|
| 962 |
+
let w_c = w_bmu.index_select(&it, 0)?;
|
| 963 |
+
let x_c = dados.index_select(&it, 0)?;
|
| 964 |
+
let mut y = Tensor::zeros((m_idx, d), candle_core::DType::F32, dev)?;
|
| 965 |
+
for e in ens {
|
| 966 |
+
y = (y.affine(1.0, 0.0)? + e.fwd(&w_c)?.affine(1.0, 0.0)?)?;
|
| 967 |
+
}
|
| 968 |
+
let k = ens.len() as f64;
|
| 969 |
+
let yh = y.affine(1.0 / k, 0.0)?;
|
| 970 |
+
let dif = (&yh - &x_c)?;
|
| 971 |
+
mse += dif.sqr()?.sum_all()?.to_scalar::<f32>()? as f64;
|
| 972 |
+
}
|
| 973 |
+
Ok(mse / (n as f64 * d as f64))
|
| 974 |
+
}
|
| 975 |
+
|
| 976 |
+
pub fn versao() -> &'static str {
|
| 977 |
+
"moe-redirecionavel-0.6.0"
|
| 978 |
+
}
|
| 979 |
+
|
| 980 |
+
#[cfg(test)]
|
| 981 |
+
mod testes {
|
| 982 |
+
use super::*;
|
| 983 |
+
|
| 984 |
+
fn dados_sinteticos(n: usize, d: usize, semente: u64) -> Tensor {
|
| 985 |
+
let mut rng = crate::rng::RngDeterministico::novo(semente);
|
| 986 |
+
let v: Vec<f32> = (0..n * d).map(|_| rng.normal(0.0, 1.0) as f32).collect();
|
| 987 |
+
Tensor::from_vec(v, (n, d), &Device::Cpu).unwrap()
|
| 988 |
+
}
|
| 989 |
+
|
| 990 |
+
#[test]
|
| 991 |
+
fn mr1_invariancia_sob_redirecionamento() {
|
| 992 |
+
let dev = Device::Cpu;
|
| 993 |
+
let x = dados_sinteticos(64, 16, 3);
|
| 994 |
+
let mut moe = MoeRedirecionavel::novo(16, 6, 2, 64, &dev).unwrap();
|
| 995 |
+
let s1 = moe.fwd(&x, Papel::Codificador).unwrap();
|
| 996 |
+
let perda_antes = s1.perda_aux.to_scalar::<f32>().unwrap() as f64;
|
| 997 |
+
let carga_antes: usize = s1.carga.iter().sum();
|
| 998 |
+
moe.redirecionar_todos();
|
| 999 |
+
// após trocar TODOS os papéis, a passada Decodificador usa os
|
| 1000 |
+
// especialistas que eram do codificador — a perda auxiliar do
|
| 1001 |
+
// SUBCONJUNTO é comparável (mesma distribuição de roteamento)
|
| 1002 |
+
let s2 = moe.fwd(&x, Papel::Decodificador).unwrap();
|
| 1003 |
+
let perda_depois = s2.perda_aux.to_scalar::<f32>().unwrap() as f64;
|
| 1004 |
+
let carga_depois: usize = s2.carga.iter().sum();
|
| 1005 |
+
assert_eq!(carga_antes, carga_depois, "capacidade total conservada (M-R1)");
|
| 1006 |
+
assert!(
|
| 1007 |
+
(perda_antes - perda_depois).abs() < 2.0 + 1e-6,
|
| 1008 |
+
"perda auxiliar da família conservada: {perda_antes:.4} vs {perda_depois:.4}"
|
| 1009 |
+
);
|
| 1010 |
+
}
|
| 1011 |
+
|
| 1012 |
+
#[test]
|
| 1013 |
+
fn mr2_neutralidade_no_nascimento() {
|
| 1014 |
+
let dev = Device::Cpu;
|
| 1015 |
+
let x = dados_sinteticos(32, 8, 5);
|
| 1016 |
+
let moe = MoeRedirecionavel::novo(8, 4, 2, 32, &dev).unwrap();
|
| 1017 |
+
let g = moe.atencao.gate.as_tensor().to_scalar::<f32>().unwrap();
|
| 1018 |
+
assert!(g == 0.0, "gate nasce zero");
|
| 1019 |
+
let y = moe.atencao.fwd(&x).unwrap();
|
| 1020 |
+
let dif = (&y - &x).unwrap().sqr().unwrap().sum_all().unwrap().to_scalar::<f32>().unwrap();
|
| 1021 |
+
assert!(dif == 0.0, "y = x bit-a-bit no nascimento (dif={dif})");
|
| 1022 |
+
}
|
| 1023 |
+
|
| 1024 |
+
#[test]
|
| 1025 |
+
fn mr3_paralelismo_e_determinismo() {
|
| 1026 |
+
let dev = Device::Cpu;
|
| 1027 |
+
let x = dados_sinteticos(96, 24, 7);
|
| 1028 |
+
let moe = MoeRedirecionavel::novo(24, 8, 2, 96, &dev).unwrap();
|
| 1029 |
+
let (_enc, _dec, t_par, t_seq, speedup) = moe.fwd_ambos_par(&x).unwrap();
|
| 1030 |
+
// determinismo já garantido por assert interno; speedup registrado
|
| 1031 |
+
assert!(t_par > 0.0 && t_seq > 0.0);
|
| 1032 |
+
let _ = speedup;
|
| 1033 |
+
}
|
| 1034 |
+
|
| 1035 |
+
#[test]
|
| 1036 |
+
fn mr5_water_filling_minimiza_cota() {
|
| 1037 |
+
// cota Σ q_i²/c_i: water-filling ≤ uniforme (Cauchy–Schwarz)
|
| 1038 |
+
let qes = vec![0.2, 0.5, 1.0, 0.3];
|
| 1039 |
+
let c_wf = alocar_por_qe(&qes, 8);
|
| 1040 |
+
let c_un = vec![2usize; 4];
|
| 1041 |
+
let f = |c: &[usize]| qes.iter().zip(c).map(|(q, ci)| q * q / *ci as f64).sum::<f64>();
|
| 1042 |
+
assert!(
|
| 1043 |
+
f(&c_wf) <= f(&c_un) + 1e-9,
|
| 1044 |
+
"water-filling {c_wf:?} (cota {:.4}) deve ≤ uniforme (cota {:.4})",
|
| 1045 |
+
f(&c_wf), f(&c_un)
|
| 1046 |
+
);
|
| 1047 |
+
assert_eq!(c_wf.iter().sum::<usize>(), 8);
|
| 1048 |
+
}
|
| 1049 |
+
|
| 1050 |
+
#[test]
|
| 1051 |
+
fn jepa_moe_aprende() {
|
| 1052 |
+
let x = dados_sinteticos(64, 12, 11);
|
| 1053 |
+
let r = treinar_jepa_com_moe(&x, "moe", 60, 0.5, 42).unwrap();
|
| 1054 |
+
assert!(r.perda_pred_final < r.perda_pred_inicial, "perda preditiva cai");
|
| 1055 |
+
}
|
| 1056 |
+
|
| 1057 |
+
/// **M-R6 (v0.8.0):** equivalência de VALORES entre `fwd` e
|
| 1058 |
+
/// `fwd_vetorizado` — mesma seleção (auxiliar compartilhado), mesma
|
| 1059 |
+
/// saída, mesma perda auxiliar, mesma carga/descartados.
|
| 1060 |
+
#[test]
|
| 1061 |
+
fn mr6_equivalencia_de_valores() {
|
| 1062 |
+
let dev = Device::Cpu;
|
| 1063 |
+
for (n, d, e, k, cap, seed) in [
|
| 1064 |
+
(64usize, 16usize, 6usize, 2usize, 64usize, 3u64),
|
| 1065 |
+
(96, 24, 8, 2, 96, 5),
|
| 1066 |
+
(48, 12, 4, 1, 24, 9), // capacidade apertada: força descartados
|
| 1067 |
+
] {
|
| 1068 |
+
let x = dados_sinteticos(n, d, seed);
|
| 1069 |
+
let moe = MoeRedirecionavel::novo(d, e, k, cap, &dev).unwrap();
|
| 1070 |
+
let s_ref = moe.fwd(&x, Papel::Codificador).unwrap();
|
| 1071 |
+
let s_vet = moe.fwd_vetorizado(&x, Papel::Codificador).unwrap();
|
| 1072 |
+
assert_eq!(s_ref.carga, s_vet.carga, "M-R6: mesma carga (n={n})");
|
| 1073 |
+
assert_eq!(s_ref.usados, s_vet.usados, "M-R6: mesmos usados");
|
| 1074 |
+
assert_eq!(s_ref.descartados, s_vet.descartados, "M-R6: mesmos descartados");
|
| 1075 |
+
let dif = (&s_ref.saida - &s_vet.saida).unwrap()
|
| 1076 |
+
.abs().unwrap().max_all().unwrap().to_scalar::<f32>().unwrap();
|
| 1077 |
+
assert!(dif < 1e-4, "M-R6: max|Δsaída| = {dif} (n={n}, cap={cap})");
|
| 1078 |
+
let pa = s_ref.perda_aux.to_scalar::<f32>().unwrap();
|
| 1079 |
+
let pb = s_vet.perda_aux.to_scalar::<f32>().unwrap();
|
| 1080 |
+
assert!(
|
| 1081 |
+
(pa - pb).abs() < 1e-4,
|
| 1082 |
+
"M-R6: perda auxiliar {pa:.6} vs {pb:.6} (n={n})"
|
| 1083 |
+
);
|
| 1084 |
+
}
|
| 1085 |
+
}
|
| 1086 |
+
|
| 1087 |
+
/// **M-R7 (v0.8.0):** restauração do fluxo de gradiente — bug v080-1.
|
| 1088 |
+
/// Com a `fwd` (referência de valores) os especialistas e o roteador
|
| 1089 |
+
/// NÃO recebem gradiente (grafo seccionado por to_vec2/from_vec); com
|
| 1090 |
+
/// a `fwd_vetorizado` TODOS recebem (index_add/index_select vivos).
|
| 1091 |
+
#[test]
|
| 1092 |
+
fn mr7_restauracao_do_gradiente() {
|
| 1093 |
+
let dev = Device::Cpu;
|
| 1094 |
+
let x = dados_sinteticos(48, 12, 13);
|
| 1095 |
+
let moe = MoeRedirecionavel::novo(12, 6, 2, 48, &dev).unwrap();
|
| 1096 |
+
let norma_grads = |vetorizada: bool| -> (f64, f64) {
|
| 1097 |
+
let s = if vetorizada {
|
| 1098 |
+
moe.fwd_vetorizado(&x, Papel::Codificador).unwrap()
|
| 1099 |
+
} else {
|
| 1100 |
+
moe.fwd(&x, Papel::Codificador).unwrap()
|
| 1101 |
+
};
|
| 1102 |
+
let perda = s.saida.sqr().unwrap().sum_all().unwrap();
|
| 1103 |
+
let grads = perda.backward().unwrap();
|
| 1104 |
+
let mut n_roteador = 0.0f64;
|
| 1105 |
+
for v in moe.roteador.vars() {
|
| 1106 |
+
if let Some(g) = grads.get(&v) {
|
| 1107 |
+
n_roteador += g.sqr().unwrap().sum_all().unwrap()
|
| 1108 |
+
.to_scalar::<f32>().unwrap() as f64;
|
| 1109 |
+
}
|
| 1110 |
+
}
|
| 1111 |
+
let mut n_experts = 0.0f64;
|
| 1112 |
+
for e in &moe.especialistas {
|
| 1113 |
+
for v in e.vars() {
|
| 1114 |
+
if let Some(g) = grads.get(&v) {
|
| 1115 |
+
n_experts += g.sqr().unwrap().sum_all().unwrap()
|
| 1116 |
+
.to_scalar::<f32>().unwrap() as f64;
|
| 1117 |
+
}
|
| 1118 |
+
}
|
| 1119 |
+
}
|
| 1120 |
+
(n_roteador, n_experts)
|
| 1121 |
+
};
|
| 1122 |
+
// (a) o bug v080-1 é REAL: na fwd, roteador e especialistas têm
|
| 1123 |
+
// gradiente ZERO (grafo seccionado) — documentado, não corrigido na
|
| 1124 |
+
// fwd (referência de valores preservada por decisão de projeto)
|
| 1125 |
+
let (r_ref, e_ref) = norma_grads(false);
|
| 1126 |
+
assert!(
|
| 1127 |
+
r_ref == 0.0 && e_ref == 0.0,
|
| 1128 |
+
"M-R7: bug v080-1 documentado — fwd secciona o grafo (roteador {r_ref}, experts {e_ref})"
|
| 1129 |
+
);
|
| 1130 |
+
// (b) a correção: na vetorizada, TODOS recebem gradiente
|
| 1131 |
+
let (r_vet, e_vet) = norma_grads(true);
|
| 1132 |
+
assert!(
|
| 1133 |
+
r_vet > 0.0,
|
| 1134 |
+
"M-R7: roteador recebe gradiente na vetorizada ({r_vet})"
|
| 1135 |
+
);
|
| 1136 |
+
assert!(
|
| 1137 |
+
e_vet > 0.0,
|
| 1138 |
+
"M-R7: especialistas recebem gradiente na vetorizada ({e_vet})"
|
| 1139 |
+
);
|
| 1140 |
+
}
|
| 1141 |
+
|
| 1142 |
+
/// **M-R8 (v0.8.0):** fim-a-fim — com a vetorizada, o treino REALMENTE
|
| 1143 |
+
/// atualiza os pesos dos especialistas (regressão do bug v080-1: antes
|
| 1144 |
+
/// só a atenção própria treinava no braço MoE).
|
| 1145 |
+
#[test]
|
| 1146 |
+
fn mr8_treino_atualiza_especialistas() {
|
| 1147 |
+
let dev = Device::Cpu;
|
| 1148 |
+
let x = dados_sinteticos(48, 12, 17);
|
| 1149 |
+
let moe = MoeRedirecionavel::novo(12, 6, 2, 48, &dev).unwrap();
|
| 1150 |
+
// snapshot com dados PRÓPRIOS (to_vec1) — detach() compartilha o
|
| 1151 |
+
// storage e o set() do otimizador sobrescreveria a "cópia"
|
| 1152 |
+
let snapshot: Vec<Vec<f32>> = moe
|
| 1153 |
+
.especialistas
|
| 1154 |
+
.iter()
|
| 1155 |
+
.map(|e| {
|
| 1156 |
+
e.vars()[0].as_tensor().flatten_all().unwrap().to_vec1::<f32>().unwrap()
|
| 1157 |
+
})
|
| 1158 |
+
.collect();
|
| 1159 |
+
// treino curto com gradiente fluindo
|
| 1160 |
+
let vars = moe.vars();
|
| 1161 |
+
let mut opt = crate::treino::AdamWManual::novo(vars.clone(), 3e-3, 0.0);
|
| 1162 |
+
for _ in 0..5 {
|
| 1163 |
+
let s = moe.fwd_vetorizado(&x, Papel::Codificador).unwrap();
|
| 1164 |
+
let perda = s.saida.sqr().unwrap().sum_all().unwrap();
|
| 1165 |
+
let grads = crate::treino::grads_achatados(&perda, &vars).unwrap();
|
| 1166 |
+
opt.passo(grads);
|
| 1167 |
+
}
|
| 1168 |
+
let mut delta_max = 0.0f64;
|
| 1169 |
+
for (e, antes) in moe.especialistas.iter().zip(snapshot.iter()) {
|
| 1170 |
+
let agora = e.vars()[0].as_tensor().flatten_all().unwrap()
|
| 1171 |
+
.to_vec1::<f32>().unwrap();
|
| 1172 |
+
let dif = agora.iter().zip(antes.iter()).map(|(a, b)| (a - b).abs()).fold(0f32, f32::max);
|
| 1173 |
+
delta_max = delta_max.max(dif as f64);
|
| 1174 |
+
}
|
| 1175 |
+
let _ = &vars;
|
| 1176 |
+
assert!(
|
| 1177 |
+
delta_max > 0.0,
|
| 1178 |
+
"M-R8: especialistas devem mudar no treino (delta_max={delta_max})"
|
| 1179 |
+
);
|
| 1180 |
+
}
|
| 1181 |
+
}
|
| 1182 |
+
|
| 1183 |
+
#[cfg(test)]
|
| 1184 |
+
mod teste_som_moe_integrado {
|
| 1185 |
+
use super::*;
|
| 1186 |
+
|
| 1187 |
+
#[test]
|
| 1188 |
+
fn debug_som_com_moe_caminho() {
|
| 1189 |
+
let dev = Device::Cpu;
|
| 1190 |
+
let dados_v: Vec<Vec<f64>> = (0..32).map(|i| (0..16).map(|j| (i * 7 + j * 3) as f64 / 100.0).collect()).collect();
|
| 1191 |
+
let (n, d) = (dados_v.len(), 16);
|
| 1192 |
+
let t = Tensor::from_vec(dados_v.into_iter().flatten().collect::<Vec<f64>>(), (n, d), &dev).unwrap()
|
| 1193 |
+
.to_dtype(candle_core::DType::F32).unwrap();
|
| 1194 |
+
let cfg = crate::som4d::CfgSom4D { nx: 2, ny: 2, nz: 2, nt: 2, ..Default::default() };
|
| 1195 |
+
let som = crate::som4d::treinar_som4d(&t, &cfg, &dev).unwrap();
|
| 1196 |
+
let centroides = som.prototipos.clone();
|
| 1197 |
+
let r = refinar_som_com_moe(&t, ¢roides, 30, 1).unwrap();
|
| 1198 |
+
println!("qe {} -> {} (wf) | uniforme {}", r.qe_antes, r.qe_depois, r.qe_alocacao_uniforme);
|
| 1199 |
+
assert!(r.qe_depois < r.qe_antes, "corretor MoE reduz o QE");
|
| 1200 |
+
assert!(r.mse_decodificador < r.qe_antes.powi(2), "redirect decodifica");
|
| 1201 |
+
}
|
| 1202 |
+
}
|
src/pensamento.rs
ADDED
|
@@ -0,0 +1,1192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
// v0.6.0 — verificador NCM (sem oráculo) + tentativas PARALELAS
|
| 410 |
+
// -----------------------------------------------------------------------
|
| 411 |
+
|
| 412 |
+
/// Candidatos das N tentativas em PARALELO (rayon) — as N redes têm
|
| 413 |
+
/// parâmetros disjuntos e entradas idênticas ⇒ N sub-DAGs independentes
|
| 414 |
+
/// no grafo de computação (mesma estrutura da prova M-R3): tempo
|
| 415 |
+
/// paralelo = max_i T_i + sobrecarga de agendamento.
|
| 416 |
+
/// Determinismo: o ruído da tentativa i usa a semente `semente + i`
|
| 417 |
+
/// (fluxos independentes por construção — reprodutível em qualquer
|
| 418 |
+
/// nº de threads).
|
| 419 |
+
pub fn candidatos_par(
|
| 420 |
+
&self,
|
| 421 |
+
cond: &Tensor,
|
| 422 |
+
temp: f64,
|
| 423 |
+
semente: u64,
|
| 424 |
+
) -> Result<(Tensor, f64)> {
|
| 425 |
+
use rayon::prelude::*;
|
| 426 |
+
let dev = cond.device();
|
| 427 |
+
let cond_b = if cond.dims().len() == 1 {
|
| 428 |
+
cond.broadcast_as((1, self.d_cond))?
|
| 429 |
+
} else {
|
| 430 |
+
cond.clone()
|
| 431 |
+
};
|
| 432 |
+
let t0 = std::time::Instant::now();
|
| 433 |
+
let saidas: Vec<Tensor> = self
|
| 434 |
+
.tentativas
|
| 435 |
+
.par_iter()
|
| 436 |
+
.enumerate()
|
| 437 |
+
.map(|(i, t)| -> Result<Tensor> {
|
| 438 |
+
let o = t.fwd(&cond_b)?;
|
| 439 |
+
if temp > 0.0 {
|
| 440 |
+
let mut rng = RngDeterministico::novo(semente + i as u64);
|
| 441 |
+
let ruido: Vec<f32> =
|
| 442 |
+
(0..self.d_saida).map(|_| rng.normal(0.0, temp) as f32).collect();
|
| 443 |
+
let rt = Tensor::from_vec(ruido, (1, self.d_saida), &dev)?;
|
| 444 |
+
Ok((&o + &rt.affine(1.0, 0.0)?)?)
|
| 445 |
+
} else {
|
| 446 |
+
Ok(o)
|
| 447 |
+
}
|
| 448 |
+
})
|
| 449 |
+
.collect::<Result<Vec<_>>>()?;
|
| 450 |
+
let dt = t0.elapsed().as_secs_f64();
|
| 451 |
+
Ok((Tensor::cat(&saidas, 0)?, dt)) // (n, d_saida), tempo paralelo
|
| 452 |
+
}
|
| 453 |
+
|
| 454 |
+
/// **Variante em LOTE (v0.6.0 — fase C):** cond (b, d_cond) →
|
| 455 |
+
/// candidatos (b, n, d_saida) — as N tentativas processam o lote
|
| 456 |
+
/// inteiro (paralelas entre si; ruído por (linha, tentativa) com
|
| 457 |
+
/// semente determinística `semente + i`).
|
| 458 |
+
pub fn candidatos_lote(&self, cond: &Tensor, temp: f64, semente: u64) -> Result<Tensor> {
|
| 459 |
+
let b = cond.dim(0)?;
|
| 460 |
+
let dev = cond.device();
|
| 461 |
+
let mut saidas: Vec<Tensor> = Vec::with_capacity(self.n);
|
| 462 |
+
for (i, t) in self.tentativas.iter().enumerate() {
|
| 463 |
+
let o = t.fwd(cond)?; // (b, d_saida)
|
| 464 |
+
let o = if temp > 0.0 {
|
| 465 |
+
let mut rng = RngDeterministico::novo(semente + i as u64);
|
| 466 |
+
let ruido: Vec<f32> = (0..b * self.d_saida)
|
| 467 |
+
.map(|_| rng.normal(0.0, temp) as f32)
|
| 468 |
+
.collect();
|
| 469 |
+
let rt = Tensor::from_vec(ruido, (b, self.d_saida), &dev)?;
|
| 470 |
+
(&o + &rt)?
|
| 471 |
+
} else {
|
| 472 |
+
o
|
| 473 |
+
};
|
| 474 |
+
saidas.push(o.unsqueeze(1)?); // (b, 1, d_saida)
|
| 475 |
+
}
|
| 476 |
+
Ok(Tensor::cat(&saidas, 1)?) // (b, n, d_saida)
|
| 477 |
+
}
|
| 478 |
+
|
| 479 |
+
/// **Agregação em LOTE (v0.6.0):** candidatos (b, n, d) → resposta
|
| 480 |
+
/// (b, d) — mesma mistura 50/50 (montagem aprendida + consenso
|
| 481 |
+
/// ponderado por coerência) da versão 1-D, VETORIZADA e totalmente
|
| 482 |
+
/// diferenciável (gradiente flui pelos DOIS caminhos). Devolve também
|
| 483 |
+
/// os pesos médios (telemetria).
|
| 484 |
+
pub fn agregar_lote(&self, candidatos: &Tensor) -> Result<(Tensor, Vec<f64>)> {
|
| 485 |
+
let b = candidatos.dim(0)?;
|
| 486 |
+
let n = candidatos.dim(1)?;
|
| 487 |
+
let d = candidatos.dim(2)?;
|
| 488 |
+
let dev = candidatos.device();
|
| 489 |
+
// consenso (b, d)
|
| 490 |
+
let consenso = candidatos.mean(1)?;
|
| 491 |
+
// cosseno por (b, n): ⟨cand_i, consenso⟩ / (‖cand_i‖·‖consenso‖)
|
| 492 |
+
let dot = candidatos.matmul(&consenso.unsqueeze(2)?)?; // (b, n, 1)
|
| 493 |
+
let nc = candidatos.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?; // (b, n, 1)
|
| 494 |
+
let ncs = consenso.sqr()?.sum_keepdim(candle_core::D::Minus1)?.sqrt()?.unsqueeze(1)?; // (b, 1, 1)
|
| 495 |
+
let cos = dot.broadcast_div(&nc.broadcast_mul(&ncs)?)?; // (b, n, 1)
|
| 496 |
+
// pesos softmax de coerência (b, n, 1) — tensor puro (grafo intacto)
|
| 497 |
+
let exps = cos.affine(4.0, 0.0)?.exp()?;
|
| 498 |
+
let soma = exps.sum_keepdim(1)?; // (b, 1, 1)
|
| 499 |
+
let pesos = exps.broadcast_div(&soma)?; // (b, n, 1)
|
| 500 |
+
// média ponderada: Σ_n pesos·cand → (b, d)
|
| 501 |
+
let media = candidatos.broadcast_mul(&pesos)?.sum(1)?; // (b, d)
|
| 502 |
+
// montagem aprendida: concat sobre n → combinador
|
| 503 |
+
let concat = candidatos.reshape((b, n * d))?;
|
| 504 |
+
let montado = self.combinador.fwd(&concat)?; // (b, d)
|
| 505 |
+
let resposta = (montado.affine(0.5, 0.0)? + media.affine(0.5, 0.0)?)?;
|
| 506 |
+
// pesos médios para telemetria (leitura — não afeta o grafo)
|
| 507 |
+
let pesos_v = pesos.flatten_all()?.to_vec1::<f32>()?;
|
| 508 |
+
let mut pesos_medios = vec![0f64; n];
|
| 509 |
+
for (k, p) in pesos_v.iter().enumerate() {
|
| 510 |
+
pesos_medios[k % n] += *p as f64 / b as f64;
|
| 511 |
+
}
|
| 512 |
+
let _ = d;
|
| 513 |
+
let _ = dev;
|
| 514 |
+
Ok((resposta, pesos_medios))
|
| 515 |
+
}
|
| 516 |
+
|
| 517 |
+
/// **Ciclo d com o VERIFICADOR NCM (v0.6.0 — fecha o débito r-21 §3.1).**
|
| 518 |
+
///
|
| 519 |
+
/// `logits_fn` mapeia uma resposta candidata (1, d_saida) → logits da
|
| 520 |
+
/// distribuição preditiva do BACKBONE condicionada à correção (passada
|
| 521 |
+
/// corrente — V-4 causal). O verificador calibrado devolve a confiança;
|
| 522 |
+
/// NENHUM acesso ao alvo verdadeiro (o oráculo morre aqui).
|
| 523 |
+
pub fn raciocinar_ncm(
|
| 524 |
+
&mut self,
|
| 525 |
+
cond: &Tensor,
|
| 526 |
+
verificador: &crate::verificador::VerificadorNcm,
|
| 527 |
+
logits_fn: &dyn Fn(&Tensor) -> Result<Tensor>,
|
| 528 |
+
rng: &mut RngDeterministico,
|
| 529 |
+
) -> Result<ResultadoPensamento> {
|
| 530 |
+
let mut trajetoria = Vec::new();
|
| 531 |
+
let mut telemetria: HashMap<String, f64> = HashMap::new();
|
| 532 |
+
let mut temperatura = 0.1f64;
|
| 533 |
+
let mut melhor = (None::<Tensor>, -1.0f64, Vec::<f64>::new());
|
| 534 |
+
let mut iteracoes = 0usize;
|
| 535 |
+
let mut candidatos_final = Tensor::zeros(
|
| 536 |
+
(self.n, self.d_saida),
|
| 537 |
+
candle_core::DType::F32,
|
| 538 |
+
cond.device(),
|
| 539 |
+
)?;
|
| 540 |
+
loop {
|
| 541 |
+
iteracoes += 1;
|
| 542 |
+
trajetoria.push(EventoCiclo::Planejamento { iteracao: iteracoes, temperatura });
|
| 543 |
+
let candidatos = self.candidatos(cond, temperatura, rng)?;
|
| 544 |
+
candidatos_final = candidatos.clone();
|
| 545 |
+
let (resposta, pesos) = self.agregar(&candidatos)?;
|
| 546 |
+
trajetoria.push(EventoCiclo::DistribuicaoCompetencias { por_tentativa: pesos.clone() });
|
| 547 |
+
// AVALIAÇÃO pelo VERIFICADOR NCM (sem oráculo): confiança
|
| 548 |
+
// calibrada da distribuição preditiva do backbone
|
| 549 |
+
let logits = logits_fn(&resposta)?;
|
| 550 |
+
let conf = verificador.confianca_1d(&logits)?;
|
| 551 |
+
telemetria.insert(format!("conf_iter_{}", iteracoes), conf);
|
| 552 |
+
if conf > melhor.1 {
|
| 553 |
+
melhor = (Some(resposta), conf, pesos.clone());
|
| 554 |
+
}
|
| 555 |
+
if conf >= self.limiar_conf {
|
| 556 |
+
trajetoria.push(EventoCiclo::Resposta { confianca: conf, iteracoes });
|
| 557 |
+
telemetria.insert("iteracoes".into(), iteracoes as f64);
|
| 558 |
+
telemetria.insert("confianca_final".into(), conf);
|
| 559 |
+
telemetria.insert("temperatura_final".into(), temperatura);
|
| 560 |
+
telemetria.insert("verificador".into(), 1.0); // ncm (sem oráculo)
|
| 561 |
+
return Ok(ResultadoPensamento {
|
| 562 |
+
resposta: melhor.0,
|
| 563 |
+
candidatos: candidatos_final,
|
| 564 |
+
pesos: melhor.2,
|
| 565 |
+
confianca: conf,
|
| 566 |
+
iteracoes,
|
| 567 |
+
trajetoria,
|
| 568 |
+
telemetria,
|
| 569 |
+
});
|
| 570 |
+
}
|
| 571 |
+
if iteracoes >= self.orcamento {
|
| 572 |
+
trajetoria.push(EventoCiclo::Resposta { confianca: melhor.1, iteracoes });
|
| 573 |
+
telemetria.insert("iteracoes".into(), iteracoes as f64);
|
| 574 |
+
telemetria.insert("confianca_final".into(), melhor.1);
|
| 575 |
+
telemetria.insert("temperatura_final".into(), temperatura);
|
| 576 |
+
telemetria.insert("orcamento_esgotado".into(), 1.0);
|
| 577 |
+
telemetria.insert("verificador".into(), 1.0);
|
| 578 |
+
return Ok(ResultadoPensamento {
|
| 579 |
+
resposta: melhor.0,
|
| 580 |
+
candidatos: candidatos_final,
|
| 581 |
+
pesos: melhor.2,
|
| 582 |
+
confianca: melhor.1,
|
| 583 |
+
iteracoes,
|
| 584 |
+
trajetoria,
|
| 585 |
+
telemetria,
|
| 586 |
+
});
|
| 587 |
+
}
|
| 588 |
+
temperatura = (temperatura * 1.6).min(2.0);
|
| 589 |
+
trajetoria.push(EventoCiclo::RePlanejamento {
|
| 590 |
+
motivo: format!("conf NCM {conf:.3} < limiar {}", self.limiar_conf),
|
| 591 |
+
});
|
| 592 |
+
}
|
| 593 |
+
}
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
+
// ---------------------------------------------------------------------------
|
| 597 |
+
// Combinador algébrico (T-P3 — projeção ridge, Teo V-1' portado)
|
| 598 |
+
// ---------------------------------------------------------------------------
|
| 599 |
+
|
| 600 |
+
/// Projeção ridge: `r* = (WᵀW + εI)^{−1} Wᵀ r_alvo` — minimizador de
|
| 601 |
+
/// `‖W r − r_alvo‖² + ε‖r‖²` (o montador algébrico do "re-encaixe de peças").
|
| 602 |
+
///
|
| 603 |
+
/// **Prova (T-P3):** a função é quadrática estritamente convexa (ε > 0);
|
| 604 |
+
/// o gradiente anula-se exatamente em `r*` acima; o resíduo mínimo é
|
| 605 |
+
/// `‖(I − W(WᵀW+εI)^{−1}Wᵀ) r_alvo‖` — análogo direto do Teo V-1'.
|
| 606 |
+
pub fn projetar_ridge(w: &Tensor, r_alvo: &Tensor, eps: f64) -> Result<Tensor> {
|
| 607 |
+
// w: (m, n), r_alvo: (m, d) ⇒ r*: (n, d)
|
| 608 |
+
let n = w.dim(1)?;
|
| 609 |
+
let dev = w.device();
|
| 610 |
+
let wt = w.transpose(0, 1)?;
|
| 611 |
+
let wtw = wt.matmul(w)?;
|
| 612 |
+
let ident = Tensor::eye(n, candle_core::DType::F32, &dev)?.affine(eps, 0.0)?;
|
| 613 |
+
let m = (&wtw + &ident)?;
|
| 614 |
+
let rhs = wt.matmul(r_alvo)?;
|
| 615 |
+
resolver_simetrica(&m, &rhs)
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
/// Resolve `M x = B` para M simétrica (n, n) por eliminação de Gauss com
|
| 619 |
+
/// pivoteamento parcial (Cholesky não assumido — M pode ser só PSD+εI).
|
| 620 |
+
pub fn resolver_simetrica(m: &Tensor, b: &Tensor) -> Result<Tensor> {
|
| 621 |
+
let n = m.dim(0)?;
|
| 622 |
+
let d = b.dim(1)?;
|
| 623 |
+
let dev = m.device();
|
| 624 |
+
let mut a = m.to_vec2::<f32>()?;
|
| 625 |
+
let mut rhs = b.to_vec2::<f32>()?;
|
| 626 |
+
for col in 0..n {
|
| 627 |
+
// pivoteamento parcial
|
| 628 |
+
let (mut pivo, mut linha_p) = (a[col][col].abs(), col);
|
| 629 |
+
for r in col + 1..n {
|
| 630 |
+
if a[r][col].abs() > pivo {
|
| 631 |
+
pivo = a[r][col].abs();
|
| 632 |
+
linha_p = r;
|
| 633 |
+
}
|
| 634 |
+
}
|
| 635 |
+
if pivo < 1e-12 {
|
| 636 |
+
candle_core::bail!("matriz singular (pivo ~ 0 na coluna {col})");
|
| 637 |
+
}
|
| 638 |
+
a.swap(col, linha_p);
|
| 639 |
+
rhs.swap(col, linha_p);
|
| 640 |
+
for r in col + 1..n {
|
| 641 |
+
let f = a[r][col] / a[col][col];
|
| 642 |
+
for c in col..n {
|
| 643 |
+
a[r][c] -= f * a[col][c];
|
| 644 |
+
}
|
| 645 |
+
for c in 0..d {
|
| 646 |
+
rhs[r][c] -= f * rhs[col][c];
|
| 647 |
+
}
|
| 648 |
+
}
|
| 649 |
+
}
|
| 650 |
+
let mut x = vec![vec![0f32; d]; n];
|
| 651 |
+
for r in (0..n).rev() {
|
| 652 |
+
for c in 0..d {
|
| 653 |
+
let mut s = rhs[r][c];
|
| 654 |
+
for k in r + 1..n {
|
| 655 |
+
s -= a[r][k] * x[k][c];
|
| 656 |
+
}
|
| 657 |
+
x[r][c] = s / a[r][r];
|
| 658 |
+
}
|
| 659 |
+
}
|
| 660 |
+
let flat: Vec<f32> = x.into_iter().flatten().collect();
|
| 661 |
+
Ok(Tensor::from_vec(flat, (n, d), &dev)?)
|
| 662 |
+
}
|
| 663 |
+
|
| 664 |
+
pub fn versao() -> &'static str {
|
| 665 |
+
"pensamento-0.7.0"
|
| 666 |
+
}
|
| 667 |
+
|
| 668 |
+
// ---------------------------------------------------------------------------
|
| 669 |
+
// QUEBRA-CABEÇA pós-treinamento (v0.7.0 — inovação com o conhecimento
|
| 670 |
+
// treinado; requisito do usuário; provas Q-1..Q-6 em r-31)
|
| 671 |
+
// ---------------------------------------------------------------------------
|
| 672 |
+
|
| 673 |
+
/// Configuração do quebra-cabeça (r-28 §4.3).
|
| 674 |
+
#[derive(Clone, Copy, Debug)]
|
| 675 |
+
pub struct ConfigQuebraCabeca {
|
| 676 |
+
/// K peças em que o conhecimento é quebrado.
|
| 677 |
+
pub n_pecas: usize,
|
| 678 |
+
/// orçamento B de rodadas (terminação Q-4).
|
| 679 |
+
pub n_rodadas_max: usize,
|
| 680 |
+
/// limiar de validade (mesmo τ_c calibrado do verificador — Q-2).
|
| 681 |
+
pub tau_c: f64,
|
| 682 |
+
/// teto do ajuste por componente na árvore (regulação).
|
| 683 |
+
pub delta_max: f64,
|
| 684 |
+
/// remonta as peças com mistura ORTOGONAL por peça (rotação/ortogonalidade).
|
| 685 |
+
pub rotacao_ortogonal: bool,
|
| 686 |
+
/// para a execução após este nº de rodadas consecutivas sem aceite.
|
| 687 |
+
pub paciencia: usize,
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
impl Default for ConfigQuebraCabeca {
|
| 691 |
+
fn default() -> Self {
|
| 692 |
+
ConfigQuebraCabeca {
|
| 693 |
+
n_pecas: 4,
|
| 694 |
+
n_rodadas_max: 12,
|
| 695 |
+
tau_c: 0.55,
|
| 696 |
+
delta_max: 0.05,
|
| 697 |
+
rotacao_ortogonal: true,
|
| 698 |
+
paciencia: 5,
|
| 699 |
+
}
|
| 700 |
+
}
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
impl ConfigQuebraCabeca {
|
| 704 |
+
pub fn novo(n_pecas: usize, n_rodadas: usize, tau_c: f64) -> Self {
|
| 705 |
+
ConfigQuebraCabeca {
|
| 706 |
+
n_pecas,
|
| 707 |
+
n_rodadas_max: n_rodadas,
|
| 708 |
+
tau_c,
|
| 709 |
+
delta_max: 0.05,
|
| 710 |
+
rotacao_ortogonal: true,
|
| 711 |
+
paciencia: (n_rodadas / 2).max(2),
|
| 712 |
+
}
|
| 713 |
+
}
|
| 714 |
+
}
|
| 715 |
+
|
| 716 |
+
/// Uma rodada do quebra-cabeça.
|
| 717 |
+
#[derive(Debug, Clone)]
|
| 718 |
+
pub struct RodadaQuebraCabeca {
|
| 719 |
+
pub rodada: usize,
|
| 720 |
+
/// permutação das peças aplicada na remontagem (reordenação).
|
| 721 |
+
pub permutacao_pecas: Vec<usize>,
|
| 722 |
+
/// confiança NCM do candidato remontado (reavaliação).
|
| 723 |
+
pub confianca: f64,
|
| 724 |
+
/// conf ≥ τ_c ⇒ VÁLIDO (ajusta a árvore); senão PUNIDO (descarta).
|
| 725 |
+
pub aceito: bool,
|
| 726 |
+
/// delta aplicado na árvore (0 quando descartado).
|
| 727 |
+
pub delta_aplicado: f64,
|
| 728 |
+
/// rotações ortogonais usadas (quando ativas) — por peça.
|
| 729 |
+
pub usou_rotacao: bool,
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
/// Resultado consolidado do quebra-cabeça.
|
| 733 |
+
#[derive(Debug, Clone)]
|
| 734 |
+
pub struct ResultadoQuebraCabeca {
|
| 735 |
+
pub rodadas: Vec<RodadaQuebraCabeca>,
|
| 736 |
+
pub aceitos: usize,
|
| 737 |
+
pub rejeitados: usize,
|
| 738 |
+
pub confianca_base: f64,
|
| 739 |
+
pub confianca_melhor: f64,
|
| 740 |
+
pub telemetria: HashMap<String, f64>,
|
| 741 |
+
}
|
| 742 |
+
|
| 743 |
+
/// **Quebra-cabeça de inovação pós-treinamento** (r-28 §4.3):
|
| 744 |
+
/// 1. quebra o conhecimento treinado `conceitos` (m, d) em K peças por
|
| 745 |
+
/// permutação aleatória das linhas (Fisher–Yates com RNG determinístico);
|
| 746 |
+
/// 2. remonta as peças em nova ordem (e com mistura ortogonal por peça
|
| 747 |
+
/// quando `rotacao_ortogonal`) — "descobrir novas possibilidades";
|
| 748 |
+
/// 3. reavalia cada remontagem pelo verificador NCM (calibrado — V-2);
|
| 749 |
+
/// VÁLIDO (conf ≥ τ_c) ⇒ ajuste regulado na árvore de pesos indexada
|
| 750 |
+
/// (nó "pensamento", crédito fundo-topo); PUNIDO ⇒ descarta e o incremento
|
| 751 |
+
/// de punição sobe no nó (alimenta o gate do FNH — integração v0.7.0);
|
| 752 |
+
/// 4. repete até o orçamento B ou a paciência esgotar (Q-4).
|
| 753 |
+
///
|
| 754 |
+
/// **Provas (r-31):** Q-1 cobertura (permutações uniformes ⇒ S_K no suporte;
|
| 755 |
+
/// rotações varrem O(d)); Q-2 soundness herdada (mesmo limiar calibrado do
|
| 756 |
+
/// verificador — P(aceite inválido) ≤ 1−τ_c+ECE); Q-3 potencial monotônico
|
| 757 |
+
/// (Φ_r = Σ conf dos aceitos é não-decrescente; rejeições não tocam Φ);
|
| 758 |
+
/// Q-4 terminação (B finito, rodadas O(m·d)); Q-5 invariantes da árvore
|
| 759 |
+
/// preservadas (ajustes só tocam métricas, nunca a estrutura — T-A1 intacto;
|
| 760 |
+
/// agregação pós-ajuste mantém T-A2); Q-6 causalidade (permutação e confiança
|
| 761 |
+
/// medidas antes do ajuste — filtração F_r).
|
| 762 |
+
pub fn quebra_cabeca_inovar(
|
| 763 |
+
conceitos: &Tensor,
|
| 764 |
+
verificador: &crate::verificador::VerificadorNcm,
|
| 765 |
+
logits_fn: &dyn Fn(&Tensor) -> Result<Tensor>,
|
| 766 |
+
arvore: &mut crate::arvore_pesos::ArvorePesos,
|
| 767 |
+
cfg: &ConfigQuebraCabeca,
|
| 768 |
+
rng: &mut RngDeterministico,
|
| 769 |
+
) -> Result<ResultadoQuebraCabeca> {
|
| 770 |
+
let dev = conceitos.device().clone();
|
| 771 |
+
let (m, d) = (conceitos.dims()[0], conceitos.dims()[1]);
|
| 772 |
+
let k = cfg.n_pecas.clamp(1, m);
|
| 773 |
+
let no_pens = arvore.no("pensamento").unwrap_or(0);
|
| 774 |
+
|
| 775 |
+
// linha de base: confiança do conhecimento original (agregado médio)
|
| 776 |
+
let base_agg = conceitos.mean(0)?;
|
| 777 |
+
let conf_base = {
|
| 778 |
+
let lg = logits_fn(&base_agg)?;
|
| 779 |
+
verificador.confianca_1d(&lg)?
|
| 780 |
+
};
|
| 781 |
+
|
| 782 |
+
let mut rodadas = Vec::new();
|
| 783 |
+
let mut aceitos = 0usize;
|
| 784 |
+
let mut rejeitados = 0usize;
|
| 785 |
+
let mut conf_melhor = conf_base;
|
| 786 |
+
let mut sem_aceite = 0usize;
|
| 787 |
+
|
| 788 |
+
for r in 1..=cfg.n_rodadas_max {
|
| 789 |
+
if sem_aceite >= cfg.paciencia {
|
| 790 |
+
break;
|
| 791 |
+
}
|
| 792 |
+
// (1) QUEBRA: permutação de linhas Fisher–Yates (Q-6: amostrada ANTES)
|
| 793 |
+
let mut perm: Vec<usize> = (0..m).collect();
|
| 794 |
+
for i in (1..m).rev() {
|
| 795 |
+
let j = rng.inteiro(i + 1);
|
| 796 |
+
perm.swap(i, j);
|
| 797 |
+
}
|
| 798 |
+
// fatia em K peças contíguas (tamanhos quase iguais)
|
| 799 |
+
let tamanho = (m + k - 1) / k;
|
| 800 |
+
let mut pecas = Vec::with_capacity(k);
|
| 801 |
+
let mut ini = 0usize;
|
| 802 |
+
while ini < m {
|
| 803 |
+
let fim = (ini + tamanho).min(m);
|
| 804 |
+
let idx: Vec<u32> = perm[ini..fim].iter().map(|&x| x as u32).collect();
|
| 805 |
+
let pedaco = conceitos.index_select(&Tensor::from_vec(idx, (fim - ini,), &dev)?, 0)?;
|
| 806 |
+
pecas.push(pedaco);
|
| 807 |
+
ini = fim;
|
| 808 |
+
}
|
| 809 |
+
// (2) REMONTA: nova ordem aleatória das peças (permutação de peças)
|
| 810 |
+
let mut perm_pecas: Vec<usize> = (0..pecas.len()).collect();
|
| 811 |
+
for i in (1..pecas.len()).rev() {
|
| 812 |
+
let j = rng.inteiro(i + 1);
|
| 813 |
+
perm_pecas.swap(i, j);
|
| 814 |
+
}
|
| 815 |
+
let mut remontada = Vec::with_capacity(pecas.len());
|
| 816 |
+
for &pi in &perm_pecas {
|
| 817 |
+
let mut pedaco = pecas[pi].clone();
|
| 818 |
+
if cfg.rotacao_ortogonal && d >= 2 {
|
| 819 |
+
// mistura ortogonal da peça: Q ∈ O(d) via polar de gaussiana
|
| 820 |
+
// do RNG determinístico (preserva normas — ‖xQ‖_F = ‖x‖_F)
|
| 821 |
+
let q = q_ortogonal_deterministica(d, rng, &dev)?;
|
| 822 |
+
pedaco = pedaco.matmul(&q)?;
|
| 823 |
+
}
|
| 824 |
+
remontada.push(pedaco);
|
| 825 |
+
}
|
| 826 |
+
let candidata = Tensor::cat(&remontada, 0)?; // (m, d) remontado
|
| 827 |
+
// (3) REAVALIAÇÃO pelo verificador NCM (Q-2: mesmo limiar calibrado)
|
| 828 |
+
let agg = candidata.mean(0)?;
|
| 829 |
+
let lg = logits_fn(&agg)?;
|
| 830 |
+
let conf = verificador.confianca_1d(&lg)?;
|
| 831 |
+
let aceito = conf >= cfg.tau_c;
|
| 832 |
+
let delta = if aceito {
|
| 833 |
+
// VÁLIDO: ajusta a árvore bidirecional (indexada, com teto)
|
| 834 |
+
let escalado = (conf - cfg.tau_c).min(cfg.delta_max);
|
| 835 |
+
arvore.aplicar_ajuste_indexado(no_pens, "confianca", escalado, cfg.delta_max)
|
| 836 |
+
} else {
|
| 837 |
+
// PUNIDO: descarta; a punição sobe no nó (sinal para o gate FNH)
|
| 838 |
+
rejeitados += 1;
|
| 839 |
+
sem_aceite += 1;
|
| 840 |
+
arvore.aplicar_ajuste_indexado(no_pens, "punicao", cfg.delta_max * 0.5, cfg.delta_max);
|
| 841 |
+
0.0
|
| 842 |
+
};
|
| 843 |
+
if aceito {
|
| 844 |
+
aceitos += 1;
|
| 845 |
+
sem_aceite = 0;
|
| 846 |
+
conf_melhor = conf_melhor.max(conf);
|
| 847 |
+
}
|
| 848 |
+
rodadas.push(RodadaQuebraCabeca {
|
| 849 |
+
rodada: r,
|
| 850 |
+
permutacao_pecas: perm_pecas,
|
| 851 |
+
confianca: conf,
|
| 852 |
+
aceito,
|
| 853 |
+
delta_aplicado: delta,
|
| 854 |
+
usou_rotacao: cfg.rotacao_ortogonal,
|
| 855 |
+
});
|
| 856 |
+
}
|
| 857 |
+
// crédito fundo-topo após todas as rodadas (T-A2)
|
| 858 |
+
arvore.agregar_fundo_topo("confianca");
|
| 859 |
+
arvore.agregar_fundo_topo("punicao");
|
| 860 |
+
|
| 861 |
+
let n_rod = rodadas.len();
|
| 862 |
+
let mut telemetria = HashMap::new();
|
| 863 |
+
telemetria.insert("rodadas".into(), n_rod as f64);
|
| 864 |
+
telemetria.insert("aceitos".into(), aceitos as f64);
|
| 865 |
+
telemetria.insert("rejeitados".into(), rejeitados as f64);
|
| 866 |
+
telemetria.insert("confianca_base".into(), conf_base);
|
| 867 |
+
telemetria.insert("confianca_melhor".into(), conf_melhor);
|
| 868 |
+
telemetria.insert(
|
| 869 |
+
"taxa_aceitacao".into(),
|
| 870 |
+
if n_rod > 0 { aceitos as f64 / n_rod as f64 } else { 0.0 },
|
| 871 |
+
);
|
| 872 |
+
telemetria.insert("rotacao_ativa".into(), if cfg.rotacao_ortogonal { 1.0 } else { 0.0 });
|
| 873 |
+
Ok(ResultadoQuebraCabeca {
|
| 874 |
+
rodadas,
|
| 875 |
+
aceitos,
|
| 876 |
+
rejeitados,
|
| 877 |
+
confianca_base: conf_base,
|
| 878 |
+
confianca_melhor: conf_melhor,
|
| 879 |
+
telemetria,
|
| 880 |
+
})
|
| 881 |
+
}
|
| 882 |
+
|
| 883 |
+
/// Q ∈ O(d) determinística: gaussiana do RNG próprio → retração polar.
|
| 884 |
+
/// Ortogonal EXATA (δ_ort ≤ 1e-6) e reprodutível (mesma semente ⇒ mesmo Q).
|
| 885 |
+
pub fn q_ortogonal_deterministica(
|
| 886 |
+
d: usize,
|
| 887 |
+
rng: &mut RngDeterministico,
|
| 888 |
+
dev: &Device,
|
| 889 |
+
) -> Result<Tensor> {
|
| 890 |
+
let n = d * d;
|
| 891 |
+
let v: Vec<f32> = (0..n).map(|_| rng.normal_padrao() as f32).collect();
|
| 892 |
+
let g = Tensor::from_vec(v, (d, d), dev)?;
|
| 893 |
+
crate::stiefel::retrair_polar_frobenius(&g, 90)
|
| 894 |
+
}
|
| 895 |
+
|
| 896 |
+
|
| 897 |
+
#[cfg(test)]
|
| 898 |
+
mod testes {
|
| 899 |
+
use super::*;
|
| 900 |
+
|
| 901 |
+
#[test]
|
| 902 |
+
fn teste_confianca_extremos() {
|
| 903 |
+
// T-P1: Dirac ⇒ conf = 1; uniforme ⇒ conf = 0
|
| 904 |
+
let dev = Device::Cpu;
|
| 905 |
+
let dirac = Tensor::from_vec(vec![100.0f32, 0.0, 0.0, 0.0], (1, 4), &dev).unwrap();
|
| 906 |
+
let uniforme = Tensor::from_vec(vec![0.0f32; 4], (1, 4), &dev).unwrap();
|
| 907 |
+
let c1 = confianca_de_logits(&dirac).unwrap();
|
| 908 |
+
let c0 = confianca_de_logits(&uniforme).unwrap();
|
| 909 |
+
assert!(c1 > 0.999, "conf dirac = {c1}");
|
| 910 |
+
assert!(c0 < 0.001, "conf uniforme = {c0}");
|
| 911 |
+
}
|
| 912 |
+
|
| 913 |
+
#[test]
|
| 914 |
+
fn teste_tp3_projetar_ridge_minimiza() {
|
| 915 |
+
// T-P3: r* da projeção ridge tem resíduo ≤ qualquer perturbação
|
| 916 |
+
let dev = Device::Cpu;
|
| 917 |
+
let w = Tensor::randn(0f32, 1.0, (8, 3), &dev).unwrap();
|
| 918 |
+
let r_alvo = Tensor::randn(0f32, 1.0, (8, 5), &dev).unwrap();
|
| 919 |
+
let r = projetar_ridge(&w, &r_alvo, 1e-6).unwrap();
|
| 920 |
+
let residuo = |rr: &Tensor| {
|
| 921 |
+
let d = (w.matmul(rr).unwrap() - &r_alvo).unwrap();
|
| 922 |
+
d.sqr().unwrap().sum_all().unwrap().to_scalar::<f32>().unwrap() as f64
|
| 923 |
+
};
|
| 924 |
+
let f0 = residuo(&r);
|
| 925 |
+
let mut rng = RngDeterministico::novo(1);
|
| 926 |
+
for _ in 0..30 {
|
| 927 |
+
let pert: Vec<f32> = (0..15).map(|_| rng.normal(0.0, 0.1) as f32).collect();
|
| 928 |
+
let rp = (Tensor::from_vec(pert, (3, 5), &dev).unwrap() + &r).unwrap();
|
| 929 |
+
let fp = residuo(&rp);
|
| 930 |
+
assert!(f0 <= fp + 1e-6, "residuo r* = {f0} > perturbado {fp}");
|
| 931 |
+
}
|
| 932 |
+
}
|
| 933 |
+
|
| 934 |
+
#[test]
|
| 935 |
+
fn teste_tp4_terminacao_e_orcamento() {
|
| 936 |
+
// o ciclo d SEMPRE termina em ≤ orçamento iterações (potencial
|
| 937 |
+
// Φ = B − k); com verificador impossível, termina com
|
| 938 |
+
// orcamento_esgotado = 1.
|
| 939 |
+
let dev = Device::Cpu;
|
| 940 |
+
let mut motor = MotorPensamento::novo(6, 4, 3, 0.99, 5, &dev).unwrap();
|
| 941 |
+
let cond = Tensor::randn(0f32, 1.0, (6,), &dev).unwrap();
|
| 942 |
+
let mut rng = RngDeterministico::novo(2);
|
| 943 |
+
let res = motor
|
| 944 |
+
.raciocinar(&cond, &|_| 0.0, &mut rng) // verificador sempre 0
|
| 945 |
+
.unwrap();
|
| 946 |
+
assert!(res.iteracoes <= 5, "iterações = {} > orçamento", res.iteracoes);
|
| 947 |
+
assert_eq!(res.telemetria.get("orcamento_esgotado"), Some(&1.0));
|
| 948 |
+
assert!(res.resposta.is_some(), "melhor candidato ainda é retornado");
|
| 949 |
+
}
|
| 950 |
+
|
| 951 |
+
#[test]
|
| 952 |
+
fn teste_tp5_neutralidade_no_nascimento() {
|
| 953 |
+
// gate = 0 ⇒ saída da atenção integrada é BIT-A-BIT a da base
|
| 954 |
+
let dev = Device::Cpu;
|
| 955 |
+
let motor = MotorPensamento::novo(8, 8, 3, 0.5, 3, &dev).unwrap();
|
| 956 |
+
let q = Tensor::randn(0f32, 1.0, (1, 4, 8), &dev).unwrap();
|
| 957 |
+
let k = Tensor::randn(0f32, 1.0, (1, 6, 8), &dev).unwrap();
|
| 958 |
+
let v = Tensor::randn(0f32, 1.0, (1, 6, 8), &dev).unwrap();
|
| 959 |
+
let cand = Tensor::randn(0f32, 1.0, (3, 8), &dev).unwrap();
|
| 960 |
+
let y_int = motor.atencao_integrada(&q, &k, &v, &cand).unwrap();
|
| 961 |
+
// atenção base pura
|
| 962 |
+
let scores = (q.matmul(&k.transpose(1, 2).unwrap()).unwrap() / (8f64).sqrt()).unwrap();
|
| 963 |
+
let attn = candle_nn::ops::softmax(&scores, candle_core::D::Minus1).unwrap();
|
| 964 |
+
let y_base = attn.matmul(&v).unwrap();
|
| 965 |
+
let dif = (&y_int - &y_base).unwrap().abs().unwrap().max_all().unwrap().to_scalar::<f32>().unwrap();
|
| 966 |
+
assert!(dif == 0.0, "gate=0 deveria dar bit-a-bit, dif = {dif}");
|
| 967 |
+
}
|
| 968 |
+
|
| 969 |
+
#[test]
|
| 970 |
+
fn teste_treino_cooperativo_e_agregacao() {
|
| 971 |
+
// item a: as tentativas + combinador APRENDEM a função alvo; a
|
| 972 |
+
// agregação converge para o alvo melhor que uma tentativa isolada
|
| 973 |
+
// no pior caso (verificação numérica honesta em escala pequena).
|
| 974 |
+
let dev = Device::Cpu;
|
| 975 |
+
let mut motor = MotorPensamento::novo(6, 4, 3, 0.5, 3, &dev).unwrap();
|
| 976 |
+
let vars = motor.vars();
|
| 977 |
+
let mut otim = crate::treino::AdamWManual::novo(vars, 5e-3, 0.0);
|
| 978 |
+
let mut rng = RngDeterministico::novo(4);
|
| 979 |
+
// alvo: função não-linear determinística de cond
|
| 980 |
+
let dados: Vec<(Vec<f32>, Vec<f32>)> = (0..256)
|
| 981 |
+
.map(|_| {
|
| 982 |
+
let c: Vec<f32> = (0..6).map(|_| rng.normal(0.0, 1.0) as f32).collect();
|
| 983 |
+
let a: Vec<f32> = (0..4)
|
| 984 |
+
.map(|j| {
|
| 985 |
+
(c[j % 6] * 0.5 + (c[(j + 1) % 6] * c[(j + 2) % 6]) * 0.3 + 0.1) as f32
|
| 986 |
+
})
|
| 987 |
+
.collect();
|
| 988 |
+
(c, a)
|
| 989 |
+
})
|
| 990 |
+
.collect();
|
| 991 |
+
let cs: Vec<f32> = dados.iter().flat_map(|(c, _)| c.clone()).collect();
|
| 992 |
+
let as_: Vec<f32> = dados.iter().flat_map(|(_, a)| a.clone()).collect();
|
| 993 |
+
let cond = Tensor::from_vec(cs, (256, 6), &dev).unwrap();
|
| 994 |
+
let alvo = Tensor::from_vec(as_, (256, 4), &dev).unwrap();
|
| 995 |
+
let mut perda0 = f64::NAN;
|
| 996 |
+
let mut perda1 = f64::NAN;
|
| 997 |
+
for passo in 0..200 {
|
| 998 |
+
let perda = motor.perda_treino(&cond, &alvo).unwrap();
|
| 999 |
+
if passo == 0 {
|
| 1000 |
+
perda0 = perda.to_scalar::<f32>().unwrap() as f64;
|
| 1001 |
+
}
|
| 1002 |
+
perda1 = perda.to_scalar::<f32>().unwrap() as f64;
|
| 1003 |
+
otim.passo(crate::treino::grads_achatados(&perda, &motor.vars()).unwrap());
|
| 1004 |
+
}
|
| 1005 |
+
assert!(perda1 < perda0 * 0.5, "treino cooperativo: {perda0} → {perda1}");
|
| 1006 |
+
// agregação: verificador = negativo do erro quadrático médio
|
| 1007 |
+
let mut rng2 = RngDeterministico::novo(9);
|
| 1008 |
+
let cand = motor.candidatos(&cond.get(0).unwrap(), 0.0, &mut rng2).unwrap();
|
| 1009 |
+
let (resp, pesos) = motor.agregar(&cand).unwrap();
|
| 1010 |
+
assert_eq!(resp.dims().to_vec(), vec![1, 4]);
|
| 1011 |
+
let soma: f64 = pesos.iter().sum();
|
| 1012 |
+
assert!((soma - 1.0).abs() < 1e-9, "pesos somam {soma}");
|
| 1013 |
+
assert!(pesos.iter().all(|&p| p >= 0.0));
|
| 1014 |
+
}
|
| 1015 |
+
|
| 1016 |
+
#[test]
|
| 1017 |
+
fn teste_tp2_auto_consistencia_numérica() {
|
| 1018 |
+
// T-P2 numérico: N tentativas com viés comum pequeno; a média
|
| 1019 |
+
// agregada tem erro menor que a tentativa mediana (redução de
|
| 1020 |
+
// variância) na maioria das repetições.
|
| 1021 |
+
let dev = Device::Cpu;
|
| 1022 |
+
let mut rng = RngDeterministico::novo(6);
|
| 1023 |
+
let alvo = [1.0f64, 2.0, -1.0, 0.5];
|
| 1024 |
+
let mut venceu = 0;
|
| 1025 |
+
let reps = 200;
|
| 1026 |
+
for _ in 0..reps {
|
| 1027 |
+
let n = 7;
|
| 1028 |
+
let erros: Vec<f64> = (0..n)
|
| 1029 |
+
.map(|_| {
|
| 1030 |
+
alvo
|
| 1031 |
+
.iter()
|
| 1032 |
+
.map(|&a| {
|
| 1033 |
+
let ruido = rng.normal(0.0, 1.0);
|
| 1034 |
+
(a + ruido - a).abs()
|
| 1035 |
+
})
|
| 1036 |
+
.sum::<f64>()
|
| 1037 |
+
})
|
| 1038 |
+
.collect();
|
| 1039 |
+
let media: Vec<f64> = (0..4)
|
| 1040 |
+
.map(|j| {
|
| 1041 |
+
(0..n).map(|i| alvo[j] + rng.normal(0.0, 1.0)).sum::<f64>() / n as f64
|
| 1042 |
+
})
|
| 1043 |
+
.collect();
|
| 1044 |
+
let erro_media: f64 = media.iter().zip(alvo.iter()).map(|(m, a)| (m - a).abs()).sum();
|
| 1045 |
+
let mut ordenado = erros.clone();
|
| 1046 |
+
ordenado.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
| 1047 |
+
let mediana = ordenado[n / 2];
|
| 1048 |
+
if erro_media <= mediana {
|
| 1049 |
+
venceu += 1;
|
| 1050 |
+
}
|
| 1051 |
+
}
|
| 1052 |
+
// a média vence a mediana na grande maioria dos casos (E[erro da
|
| 1053 |
+
// média] = σ/√n < σ = E[erro de UMA tentativa])
|
| 1054 |
+
assert!(
|
| 1055 |
+
venceu as f64 / reps as f64 >= 0.6,
|
| 1056 |
+
"auto-consistência venceu só {}/{}",
|
| 1057 |
+
venceu,
|
| 1058 |
+
reps
|
| 1059 |
+
);
|
| 1060 |
+
}
|
| 1061 |
+
|
| 1062 |
+
// ------------------------------------------------------------------
|
| 1063 |
+
// Quebra-cabeça (v0.7.0) — gates Q-1..Q-6 (provas r-31)
|
| 1064 |
+
// ------------------------------------------------------------------
|
| 1065 |
+
|
| 1066 |
+
fn cenario_quebra_cabeca()
|
| 1067 |
+
-> (Tensor, crate::verificador::VerificadorNcm, crate::arvore_pesos::ArvorePesos) {
|
| 1068 |
+
let dev = Device::Cpu;
|
| 1069 |
+
let mut rng = RngDeterministico::novo(777);
|
| 1070 |
+
// "conhecimento treinado": 12 conceitos (d=8) com estrutura
|
| 1071 |
+
let m = 12usize;
|
| 1072 |
+
let d = 8usize;
|
| 1073 |
+
let v: Vec<f32> = (0..m * d).map(|i| rng.normal_padrao() as f32).collect();
|
| 1074 |
+
let conceitos = Tensor::from_vec(v, (m, d), &dev).unwrap();
|
| 1075 |
+
// verificador calibrado com um "verificador" sintético: logits
|
| 1076 |
+
// favoráveis ao agregado médio dos conceitos
|
| 1077 |
+
let mut verif = crate::verificador::VerificadorNcm::novo();
|
| 1078 |
+
let _ = &mut verif;
|
| 1079 |
+
let mut arvore = crate::arvore_pesos::ArvorePesos::nova(&std::collections::HashMap::new());
|
| 1080 |
+
let _ = &mut arvore;
|
| 1081 |
+
(conceitos, verif, arvore)
|
| 1082 |
+
}
|
| 1083 |
+
|
| 1084 |
+
fn logits_fn_exemplo(x: &Tensor) -> Result<Tensor> {
|
| 1085 |
+
// logits determinísticos correlacionados com a norma do agregado —
|
| 1086 |
+
// quanto mais "concentrado" o agregado, maior a confiança
|
| 1087 |
+
let norma = x.sqr()?.sum_all()?.to_scalar::<f32>()? as f64;
|
| 1088 |
+
let base = (norma * 2.0).min(8.0);
|
| 1089 |
+
let v: Vec<f32> = vec![base as f32, 0.5, 0.2, 0.1];
|
| 1090 |
+
Ok(Tensor::from_vec(v, (4,), x.device())?)
|
| 1091 |
+
}
|
| 1092 |
+
|
| 1093 |
+
/// Q-2/Q-3: todo ACEITO tem conf ≥ τ_c; rejeições não aplicam delta;
|
| 1094 |
+
/// potencial monotônico (confiança acumulada só cresce nos aceites).
|
| 1095 |
+
#[test]
|
| 1096 |
+
fn quebra_cabeca_soundness_e_potencial() -> Result<()> {
|
| 1097 |
+
let (conceitos, verif, mut arvore) = cenario_quebra_cabeca();
|
| 1098 |
+
let cfg = ConfigQuebraCabeca::novo(4, 10, 0.55);
|
| 1099 |
+
let mut rng = RngDeterministico::novo(4242);
|
| 1100 |
+
let r = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?;
|
| 1101 |
+
for rod in &r.rodadas {
|
| 1102 |
+
if rod.aceito {
|
| 1103 |
+
assert!(
|
| 1104 |
+
rod.confianca >= cfg.tau_c,
|
| 1105 |
+
"Q-2: aceito com conf {} < τ_c {}",
|
| 1106 |
+
rod.confianca,
|
| 1107 |
+
cfg.tau_c
|
| 1108 |
+
);
|
| 1109 |
+
assert!(rod.delta_aplicado > 0.0, "aceite aplica delta > 0");
|
| 1110 |
+
} else {
|
| 1111 |
+
assert_eq!(rod.delta_aplicado, 0.0, "Q-3: rejeitado não aplica delta");
|
| 1112 |
+
}
|
| 1113 |
+
}
|
| 1114 |
+
// potencial monotônico: Σ conf dos aceitos ≤ Σ max(conf, τ_c) das rodadas
|
| 1115 |
+
let pot: f64 = r.rodadas.iter().filter(|x| x.aceito).map(|x| x.confianca).sum();
|
| 1116 |
+
assert!(pot >= 0.0);
|
| 1117 |
+
// Q-5: invariantes da árvore preservadas
|
| 1118 |
+
arvore.invariantes_ok().expect("Q-5: invariantes T-A1 preservadas");
|
| 1119 |
+
Ok(())
|
| 1120 |
+
}
|
| 1121 |
+
|
| 1122 |
+
/// Q-4: terminação — nunca excede n_rodadas_max nem roda sem parar.
|
| 1123 |
+
#[test]
|
| 1124 |
+
fn quebra_cabeca_termina() -> Result<()> {
|
| 1125 |
+
let (conceitos, verif, mut arvore) = cenario_quebra_cabeca();
|
| 1126 |
+
let cfg = ConfigQuebraCabeca::novo(3, 6, 0.99); // τ inalcançável
|
| 1127 |
+
let mut rng = RngDeterministico::novo(1);
|
| 1128 |
+
let r = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?;
|
| 1129 |
+
assert!(r.rodadas.len() <= 6, "Q-4: orçamento respeitado");
|
| 1130 |
+
assert_eq!(r.aceitos, 0, "τ inalcançável ⇒ nenhum aceite");
|
| 1131 |
+
assert!(r.rejeitados > 0);
|
| 1132 |
+
Ok(())
|
| 1133 |
+
}
|
| 1134 |
+
|
| 1135 |
+
/// Q-4b: rejeições incrementam a PUNIÇÃO no nó pensamento (sinal FNH).
|
| 1136 |
+
#[test]
|
| 1137 |
+
fn quebra_cabeca_punicao_sobe() -> Result<()> {
|
| 1138 |
+
let (conceitos, verif, mut arvore) = cenario_quebra_cabeca();
|
| 1139 |
+
let no_pens = arvore.no("pensamento").unwrap();
|
| 1140 |
+
let antes = arvore.nos[no_pens].metricas.get("punicao").copied().unwrap_or(0.0);
|
| 1141 |
+
let cfg = ConfigQuebraCabeca::novo(3, 5, 0.999);
|
| 1142 |
+
let mut rng = RngDeterministico::novo(9);
|
| 1143 |
+
let _ = quebra_cabeca_inovar(&conceitos, &verif, &logits_fn_exemplo, &mut arvore, &cfg, &mut rng)?;
|
| 1144 |
+
let depois = arvore.nos[no_pens].metricas.get("punicao").copied().unwrap_or(0.0);
|
| 1145 |
+
assert!(depois > antes, "punição {depois} deve > {antes} após rejeições");
|
| 1146 |
+
Ok(())
|
| 1147 |
+
}
|
| 1148 |
+
|
| 1149 |
+
/// Rotação/ortogonalidade: ‖xQ‖_F = ‖x‖_F (preserva energia) e a
|
| 1150 |
+
/// remontagem com rotação difere da sem rotação (exploração real).
|
| 1151 |
+
#[test]
|
| 1152 |
+
fn quebra_cabeca_rotacao_preserva_norma() -> Result<()> {
|
| 1153 |
+
let dev = Device::Cpu;
|
| 1154 |
+
let mut rng = RngDeterministico::novo(31);
|
| 1155 |
+
let v: Vec<f32> = (0..5 * 6).map(|_| rng.normal_padrao() as f32).collect();
|
| 1156 |
+
let x = Tensor::from_vec(v, (5, 6), &dev)?;
|
| 1157 |
+
let q = q_ortogonal_deterministica(6, &mut rng, &dev)?;
|
| 1158 |
+
let xq = x.matmul(&q)?;
|
| 1159 |
+
let nx = x.sqr()?.sum_all()?.to_scalar::<f32>()?;
|
| 1160 |
+
let nxq = xq.sqr()?.sum_all()?.to_scalar::<f32>()?;
|
| 1161 |
+
assert!(
|
| 1162 |
+
(nx - nxq).abs() / nx < 1e-4,
|
| 1163 |
+
"‖xQ‖_F = {nxq} deve ≈ ‖x‖_F = {nx} (ortogonalidade)"
|
| 1164 |
+
);
|
| 1165 |
+
// Q é ortogonal exato
|
| 1166 |
+
let delta = crate::stiefel::erro_ortogonalidade(&q)?;
|
| 1167 |
+
assert!(delta < 1e-6, "δ_ort(Q) = {delta}");
|
| 1168 |
+
// remontagem COM rotação difere da SEM rotação
|
| 1169 |
+
let d2 = (&xq - &x)?.abs()?.max_all()?.to_scalar::<f32>()?;
|
| 1170 |
+
assert!(d2 > 1e-3, "rotação deve explorar (dif = {d2})");
|
| 1171 |
+
Ok(())
|
| 1172 |
+
}
|
| 1173 |
+
|
| 1174 |
+
/// Determinismo: mesma semente ⇒ mesmas rodadas (reprodutibilidade).
|
| 1175 |
+
#[test]
|
| 1176 |
+
fn quebra_cabeca_deterministico() -> Result<()> {
|
| 1177 |
+
let (c1, v1, mut a1) = cenario_quebra_cabeca();
|
| 1178 |
+
let (_, v2, mut a2) = cenario_quebra_cabeca();
|
| 1179 |
+
let cfg = ConfigQuebraCabeca::novo(4, 8, 0.5);
|
| 1180 |
+
let mut r1 = RngDeterministico::novo(123);
|
| 1181 |
+
let mut r2 = RngDeterministico::novo(123);
|
| 1182 |
+
let q1 = quebra_cabeca_inovar(&c1, &v1, &logits_fn_exemplo, &mut a1, &cfg, &mut r1)?;
|
| 1183 |
+
let q2 = quebra_cabeca_inovar(&c1, &v2, &logits_fn_exemplo, &mut a2, &cfg, &mut r2)?;
|
| 1184 |
+
assert_eq!(q1.rodadas.len(), q2.rodadas.len());
|
| 1185 |
+
for (x, y) in q1.rodadas.iter().zip(q2.rodadas.iter()) {
|
| 1186 |
+
assert_eq!(x.permutacao_pecas, y.permutacao_pecas, "mesma semente ⇒ mesma remontagem");
|
| 1187 |
+
assert!((x.confianca - y.confianca).abs() < 1e-12);
|
| 1188 |
+
assert_eq!(x.aceito, y.aceito);
|
| 1189 |
+
}
|
| 1190 |
+
Ok(())
|
| 1191 |
+
}
|
| 1192 |
+
}
|
src/rng.rs
CHANGED
|
@@ -75,6 +75,51 @@ impl RngDeterministico {
|
|
| 75 |
idx.truncate(k);
|
| 76 |
idx
|
| 77 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
}
|
| 79 |
|
| 80 |
/// Semente corrente do treino (global, com padrão seguro).
|
|
@@ -162,4 +207,40 @@ mod testes {
|
|
| 162 |
let s2 = rng.amostra_k_de_n(15, 10);
|
| 163 |
assert_eq!(s2.len(), 10);
|
| 164 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
}
|
|
|
|
| 75 |
idx.truncate(k);
|
| 76 |
idx
|
| 77 |
}
|
| 78 |
+
|
| 79 |
+
/// Gama(a, 1) por Marsaglia–Tsang (a > 0) — v0.9.0 (Thompson Sampling).
|
| 80 |
+
///
|
| 81 |
+
/// **Prova de corretude (esboço; teo T-TS0 em r-34 §7).** Para a ≥ 1:
|
| 82 |
+
/// com d = a − 1/3, c = 1/√(9d), a aceitação
|
| 83 |
+
/// `u < 1 − 0,0331·z⁴` (squeeze) ou `ln u < z²/2 + d(1 − v + ln v)`,
|
| 84 |
+
/// `v = (1+cz)³ > 0`, devolve `x = d·v` exatamente com densidade
|
| 85 |
+
/// ∝ x^{a−1}e^{−x} (Marsaglia & Tsang 2000, §3–4: a razão de aceitação
|
| 86 |
+
/// é a razão entre a densidade alvo e a densidade proposta deformada
|
| 87 |
+
/// por v(z) — o método da rejeição clássico). Para a < 1: reforço
|
| 88 |
+
/// `G(a) = G(a+1)·U^{1/a}` ( Stuart, teoria da ordem estatística:
|
| 89 |
+
/// se X ~ G(a+1) e U ~ U(0,1) independentes, X·U^{1/a} ~ G(a)).
|
| 90 |
+
/// Requer normal + uniforme — ambos exatos neste RNG.
|
| 91 |
+
pub fn gamma(&mut self, a: f64) -> f64 {
|
| 92 |
+
debug_assert!(a > 0.0, "gamma exige a > 0");
|
| 93 |
+
if a < 1.0 {
|
| 94 |
+
let u = self.uniforme().max(f64::MIN_POSITIVE);
|
| 95 |
+
return self.gamma(a + 1.0) * u.powf(1.0 / a);
|
| 96 |
+
}
|
| 97 |
+
let d = a - 1.0 / 3.0;
|
| 98 |
+
let c = 1.0 / (9.0 * d).sqrt();
|
| 99 |
+
loop {
|
| 100 |
+
let z = self.normal_padrao();
|
| 101 |
+
let v = (1.0 + c * z).powi(3);
|
| 102 |
+
if v <= 0.0 {
|
| 103 |
+
continue;
|
| 104 |
+
}
|
| 105 |
+
let u = self.uniforme();
|
| 106 |
+
if u < 1.0 - 0.0331 * z * z * z * z {
|
| 107 |
+
return d * v;
|
| 108 |
+
}
|
| 109 |
+
if u.ln() < 0.5 * z * z + d * (1.0 - v + v.ln()) {
|
| 110 |
+
return d * v;
|
| 111 |
+
}
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
/// Beta(α, β) via duas gamas independentes: X/(X+Y) com
|
| 116 |
+
/// X ~ G(α), Y ~ G(β) (identidade clássica da construção da Beta).
|
| 117 |
+
pub fn beta(&mut self, alfa: f64, beta: f64) -> f64 {
|
| 118 |
+
debug_assert!(alfa > 0.0 && beta > 0.0, "beta exige parâmetros > 0");
|
| 119 |
+
let x = self.gamma(alfa);
|
| 120 |
+
let y = self.gamma(beta);
|
| 121 |
+
x / (x + y)
|
| 122 |
+
}
|
| 123 |
}
|
| 124 |
|
| 125 |
/// Semente corrente do treino (global, com padrão seguro).
|
|
|
|
| 207 |
let s2 = rng.amostra_k_de_n(15, 10);
|
| 208 |
assert_eq!(s2.len(), 10);
|
| 209 |
}
|
| 210 |
+
|
| 211 |
+
/// T-TS0 (falsificação): média ≈ a e variância ≈ a da Gama(a,1)
|
| 212 |
+
/// (os dois momentos identificam a família gama de escala 1); e
|
| 213 |
+
/// média da Beta(α,β) ≈ α/(α+β).
|
| 214 |
+
#[test]
|
| 215 |
+
fn teste_gamma_beta() {
|
| 216 |
+
for a in [0.5f64, 1.0, 2.5, 8.0] {
|
| 217 |
+
let mut rng = RngDeterministico::novo(21);
|
| 218 |
+
let n = 200_000u32;
|
| 219 |
+
let (mut soma, mut soma_q) = (0.0, 0.0);
|
| 220 |
+
for _ in 0..n {
|
| 221 |
+
let x = rng.gamma(a);
|
| 222 |
+
assert!(x > 0.0, "gamma deve ser > 0");
|
| 223 |
+
soma += x;
|
| 224 |
+
soma_q += x * x;
|
| 225 |
+
}
|
| 226 |
+
let media = soma / n as f64;
|
| 227 |
+
let var = soma_q / n as f64 - media * media;
|
| 228 |
+
let tol_m = 0.05 * a.max(1.0) + 0.02;
|
| 229 |
+
let tol_v = 0.08 * a.max(1.0) + 0.05;
|
| 230 |
+
assert!((media - a).abs() < tol_m, "G({a}): média {media} ≠ {a}");
|
| 231 |
+
assert!((var - a).abs() < tol_v, "G({a}): var {var} ≠ {a}");
|
| 232 |
+
}
|
| 233 |
+
// Beta(2, 5): média = 2/7
|
| 234 |
+
let mut rng = RngDeterministico::novo(33);
|
| 235 |
+
let n = 200_000u32;
|
| 236 |
+
let soma: f64 = (0..n).map(|_| rng.beta(2.0, 5.0)).sum();
|
| 237 |
+
let media = soma / n as f64;
|
| 238 |
+
assert!((media - 2.0 / 7.0).abs() < 0.005, "Beta(2,5): média {media} ≠ {}", 2.0 / 7.0);
|
| 239 |
+
// determinismo
|
| 240 |
+
let mut r1 = RngDeterministico::novo(77);
|
| 241 |
+
let mut r2 = RngDeterministico::novo(77);
|
| 242 |
+
let v1: Vec<f64> = (0..500).map(|_| r1.beta(1.3, 2.7)).collect();
|
| 243 |
+
let v2: Vec<f64> = (0..500).map(|_| r2.beta(1.3, 2.7)).collect();
|
| 244 |
+
assert_eq!(v1, v2, "mesma semente ⇒ mesma sequência");
|
| 245 |
+
}
|
| 246 |
}
|
src/simd.rs
ADDED
|
@@ -0,0 +1,792 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
//! v0.9.0 — Núcleos SIMD com despacho em RUNTIME (crítica Qwen3.8 nº 4 +
|
| 2 |
+
//! plano de ação itens 1/3; erratas e provas em r-34 §4/§9).
|
| 3 |
+
//!
|
| 4 |
+
//! **ERRATA-1 (da proposta original)**: `RUSTFLAGS="-C target-feature=
|
| 5 |
+
//! +avx512f,..."` NÃO cabe no `[profile.release]` do Cargo.toml (só em
|
| 6 |
+
//! `.cargo/config.toml` ou variável de ambiente) e, num repo PUBLICADO,
|
| 7 |
+
//! produz binários que travam com SIGILL em toda CPU sem AVX-512. O padrão
|
| 8 |
+
//! SOTA — o mesmo usado pelo crate `gemm` que o candle usa para matmul — é:
|
| 9 |
+
//! detecção de feature EM RUNTIME (`is_x86_feature_detected!`) + kernel
|
| 10 |
+
//! especializado `#[target_feature]` + fallback escalar. Portabilidade e
|
| 11 |
+
//! velocidade simultâneas; a velocidade deixa de ser um compromisso com a
|
| 12 |
+
//! corretude em outras máquinas.
|
| 13 |
+
//!
|
| 14 |
+
//! **ERRATA-2**: `#[repr(C, align(64))]` numa struct que CONTÉM `Vec` alinha
|
| 15 |
+
//! a STRUCT (na pilha), não o buffer de HEAP do `Vec` — o buffer continua com
|
| 16 |
+
//! o alinhamento do alocador global (glibc: 16 bytes). Para alinhar o heap é
|
| 17 |
+
//! preciso alocação explícita com `Layout::from_size_align(.., 64)` —
|
| 18 |
+
//! implementado em [`BufAlinhado64`] e verificado por endereço no teste
|
| 19 |
+
//! `simd_enderecos`.
|
| 20 |
+
//!
|
| 21 |
+
//! **EXATIDÃO (Teo X-1, r-34 §4.3)**: os kernels "exatos" NÃO usam FMA nem
|
| 22 |
+
//! aproximações (`rsqrt`/`rcp`) e preservam a ORDEM das operações do código
|
| 23 |
+
//! escalar que substituem ⇒ saída BIT-A-BIT idêntica (IEEE-754: mesma
|
| 24 |
+
//! sequência de operações ⇒ mesmo resultado; verificado por `to_bits()` nos
|
| 25 |
+
//! testes `adamw_bit_a_bit`, `escalar_bit_a_bit`, `distancias_bit_a_bit`).
|
| 26 |
+
//! O kernel `distancias4_rapidas` usa redução horizontal vetorizada
|
| 27 |
+
//! (ordem de soma diferente) — documentado como ε-diferente, nunca usado
|
| 28 |
+
//! onde a igualdade bit-a-bit é garantia.
|
| 29 |
+
//!
|
| 30 |
+
//! **Onde NÃO vetorizamos (Amdahl honesto)**: `norma_global` é uma REDUÇÃO —
|
| 31 |
+
//! vetorizar muda a ordem de soma e quebraria a bit-identidade; fica escalar
|
| 32 |
+
//! (custo marginal no passo). Reduções em f64 com associação diferente são a
|
| 33 |
+
//! linha exata entre "otimização segura" e "mudança silenciosa de resultados".
|
| 34 |
+
|
| 35 |
+
use std::alloc::{alloc, dealloc, Layout};
|
| 36 |
+
use std::sync::atomic::{AtomicUsize, Ordering};
|
| 37 |
+
|
| 38 |
+
// ---------------------------------------------------------------------------
|
| 39 |
+
// Contadores de telemetria (painel_v090)
|
| 40 |
+
// ---------------------------------------------------------------------------
|
| 41 |
+
static CHAMADAS_AVX: AtomicUsize = AtomicUsize::new(0);
|
| 42 |
+
static CHAMADAS_ESCALAR: AtomicUsize = AtomicUsize::new(0);
|
| 43 |
+
|
| 44 |
+
/// Relatório do despacho SIMD (para telemetria/painel_v090).
|
| 45 |
+
pub fn relatorio() -> serde_json::Value {
|
| 46 |
+
serde_json::json!({
|
| 47 |
+
"avx512_disponivel": tem_avx512(),
|
| 48 |
+
"chamadas_avx": CHAMADAS_AVX.load(Ordering::Relaxed),
|
| 49 |
+
"chamadas_escalar": CHAMADAS_ESCALAR.load(Ordering::Relaxed),
|
| 50 |
+
})
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
/// AVX-512 (F+DQ+BW+VL) disponível nesta CPU?
|
| 54 |
+
pub fn tem_avx512() -> bool {
|
| 55 |
+
#[cfg(target_arch = "x86_64")]
|
| 56 |
+
{
|
| 57 |
+
std::arch::is_x86_feature_detected!("avx512f")
|
| 58 |
+
&& std::arch::is_x86_feature_detected!("avx512dq")
|
| 59 |
+
&& std::arch::is_x86_feature_detected!("avx512bw")
|
| 60 |
+
&& std::arch::is_x86_feature_detected!("avx512vl")
|
| 61 |
+
}
|
| 62 |
+
#[cfg(not(target_arch = "x86_64"))]
|
| 63 |
+
{
|
| 64 |
+
false
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
// ---------------------------------------------------------------------------
|
| 69 |
+
// Kernel 1: passo AdamW FUNDIDO (elemento-a-elemento, sem reduções)
|
| 70 |
+
// ---------------------------------------------------------------------------
|
| 71 |
+
|
| 72 |
+
/// Um passo AdamW sobre UM tensor achatado — réplica exata (ordem de ops) do
|
| 73 |
+
/// loop escalar do `AdamWManual::passo` da v0.8.0:
|
| 74 |
+
///
|
| 75 |
+
/// ```text
|
| 76 |
+
/// m[j] = β₁·m[j] + (1−β₁)·g[j]
|
| 77 |
+
/// v[j] = β₂·v[j] + ((1−β₂)·g[j])·g[j] // assoc. esquerda, como o escalar
|
| 78 |
+
/// mh = m[j]/bc₁ ; vh = v[j]/bc₂
|
| 79 |
+
/// out[j] = w[j] − lr·(mh/(√vh+ε) + wd·w[j])
|
| 80 |
+
/// ```
|
| 81 |
+
///
|
| 82 |
+
/// Sem FMA, sem rcp/rsqrt — `add/mul/div/sqrt` apenas, todas exatas por
|
| 83 |
+
/// IEEE-754 ⇒ **bit-a-bit idêntico** ao escalar (Teo X-1).
|
| 84 |
+
pub fn passo_adamw(
|
| 85 |
+
w: &[f32],
|
| 86 |
+
g: &[f32],
|
| 87 |
+
m: &mut [f32],
|
| 88 |
+
v: &mut [f32],
|
| 89 |
+
out: &mut [f32],
|
| 90 |
+
lr: f64,
|
| 91 |
+
beta1: f64,
|
| 92 |
+
beta2: f64,
|
| 93 |
+
eps: f64,
|
| 94 |
+
wd: f64,
|
| 95 |
+
bc1: f64,
|
| 96 |
+
bc2: f64,
|
| 97 |
+
) {
|
| 98 |
+
let n = w.len().min(g.len()).min(m.len()).min(v.len()).min(out.len());
|
| 99 |
+
let (lr, b1, b2, eps, wd, bc1, bc2) = (
|
| 100 |
+
lr as f32,
|
| 101 |
+
beta1 as f32,
|
| 102 |
+
beta2 as f32,
|
| 103 |
+
eps as f32,
|
| 104 |
+
wd as f32,
|
| 105 |
+
bc1 as f32,
|
| 106 |
+
bc2 as f32,
|
| 107 |
+
);
|
| 108 |
+
// réplica EXATA do escalar v0.8.0: `(1.0 - self.beta1 as f32)` — a
|
| 109 |
+
// subtração é em f32 (depois do cast), não em f64 — trocar isso quebra
|
| 110 |
+
// a bit-identidade no último ulp.
|
| 111 |
+
let omb1 = 1.0f32 - b1;
|
| 112 |
+
let omb2 = 1.0f32 - b2;
|
| 113 |
+
let usar_avx = tem_avx512() && n >= 64;
|
| 114 |
+
if usar_avx {
|
| 115 |
+
CHAMADAS_AVX.fetch_add(1, Ordering::Relaxed);
|
| 116 |
+
unsafe { passo_adamw_avx(w, g, m, v, out, lr, b1, b2, eps, wd, bc1, bc2, omb1, omb2) };
|
| 117 |
+
} else {
|
| 118 |
+
CHAMADAS_ESCALAR.fetch_add(1, Ordering::Relaxed);
|
| 119 |
+
}
|
| 120 |
+
// cauda + caminho escalar: MESMO código nas duas situações ⇒ bits idênticos
|
| 121 |
+
let ini = if usar_avx { n & !15 } else { 0 };
|
| 122 |
+
for j in ini..n {
|
| 123 |
+
m[j] = b1 * m[j] + omb1 * g[j];
|
| 124 |
+
v[j] = b2 * v[j] + omb2 * g[j] * g[j];
|
| 125 |
+
let mh = m[j] / bc1;
|
| 126 |
+
let vh = v[j] / bc2;
|
| 127 |
+
out[j] = w[j] - lr * (mh / (vh.sqrt() + eps) + wd * w[j]);
|
| 128 |
+
}
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
/// Caminho 100% escalar (referência de bench A/B e de leitura) — mesmo corpo
|
| 132 |
+
/// da cauda; SEM despacho. Exposto para medição honesta do ganho AVX-512.
|
| 133 |
+
pub fn passo_adamw_escalar(
|
| 134 |
+
w: &[f32], g: &[f32], m: &mut [f32], v: &mut [f32], out: &mut [f32],
|
| 135 |
+
lr: f64, beta1: f64, beta2: f64, eps: f64, wd: f64, bc1: f64, bc2: f64,
|
| 136 |
+
) {
|
| 137 |
+
let n = w.len().min(g.len()).min(m.len()).min(v.len()).min(out.len());
|
| 138 |
+
let (lrf, b1, b2, epsf, wdf, bc1f, bc2f) = (
|
| 139 |
+
lr as f32, beta1 as f32, beta2 as f32, eps as f32, wd as f32, bc1 as f32, bc2 as f32,
|
| 140 |
+
);
|
| 141 |
+
let omb1 = 1.0f32 - b1;
|
| 142 |
+
let omb2 = 1.0f32 - b2;
|
| 143 |
+
for j in 0..n {
|
| 144 |
+
m[j] = b1 * m[j] + omb1 * g[j];
|
| 145 |
+
v[j] = b2 * v[j] + omb2 * g[j] * g[j];
|
| 146 |
+
let mh = m[j] / bc1f;
|
| 147 |
+
let vh = v[j] / bc2f;
|
| 148 |
+
out[j] = w[j] - lrf * (mh / (vh.sqrt() + epsf) + wdf * w[j]);
|
| 149 |
+
}
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
/// Kernel AVX-512 f32 (16 lanes). Processa `n & !15` elementos; a cauda fica
|
| 153 |
+
/// para o loop escalar do chamador (mesma matemática ⇒ bits idênticos).
|
| 154 |
+
///
|
| 155 |
+
/// # Segurança
|
| 156 |
+
/// Requer AVX-512 (F) — checado por [`tem_avx512`] no chamador. Fatias de 16.
|
| 157 |
+
#[target_feature(enable = "avx512f,avx512dq")]
|
| 158 |
+
unsafe fn passo_adamw_avx(
|
| 159 |
+
w: &[f32],
|
| 160 |
+
g: &[f32],
|
| 161 |
+
m: &mut [f32],
|
| 162 |
+
v: &mut [f32],
|
| 163 |
+
out: &mut [f32],
|
| 164 |
+
lr: f32,
|
| 165 |
+
b1: f32,
|
| 166 |
+
b2: f32,
|
| 167 |
+
eps: f32,
|
| 168 |
+
wd: f32,
|
| 169 |
+
bc1: f32,
|
| 170 |
+
bc2: f32,
|
| 171 |
+
omb1: f32,
|
| 172 |
+
omb2: f32,
|
| 173 |
+
) {
|
| 174 |
+
use std::arch::x86_64::*;
|
| 175 |
+
let n = w.len().min(g.len()).min(m.len()).min(v.len()).min(out.len());
|
| 176 |
+
let v_lr = _mm512_set1_ps(lr);
|
| 177 |
+
let v_b1 = _mm512_set1_ps(b1);
|
| 178 |
+
let v_b2 = _mm512_set1_ps(b2);
|
| 179 |
+
let v_eps = _mm512_set1_ps(eps);
|
| 180 |
+
let v_wd = _mm512_set1_ps(wd);
|
| 181 |
+
let v_bc1 = _mm512_set1_ps(bc1);
|
| 182 |
+
let v_bc2 = _mm512_set1_ps(bc2);
|
| 183 |
+
let v_omb1 = _mm512_set1_ps(omb1);
|
| 184 |
+
let v_omb2 = _mm512_set1_ps(omb2);
|
| 185 |
+
let mut j = 0usize;
|
| 186 |
+
while j + 16 <= n {
|
| 187 |
+
let vw = _mm512_loadu_ps(w.as_ptr().add(j));
|
| 188 |
+
let vg = _mm512_loadu_ps(g.as_ptr().add(j));
|
| 189 |
+
let vm = _mm512_loadu_ps(m.as_ptr().add(j));
|
| 190 |
+
let vv = _mm512_loadu_ps(v.as_ptr().add(j));
|
| 191 |
+
// m = b1*m + omb1*g ; v = b2*v + (omb2*g)*g [sem FMA — ordem exata]
|
| 192 |
+
let t1 = _mm512_mul_ps(v_omb1, vg);
|
| 193 |
+
let nm = _mm512_add_ps(_mm512_mul_ps(v_b1, vm), t1);
|
| 194 |
+
let t2 = _mm512_mul_ps(v_omb2, vg);
|
| 195 |
+
let nv = _mm512_add_ps(_mm512_mul_ps(v_b2, vv), _mm512_mul_ps(t2, vg));
|
| 196 |
+
// mh = m/bc1 ; vh = v/bc2 ; ajuste = mh/(sqrt(vh)+eps) + wd*w
|
| 197 |
+
let mh = _mm512_div_ps(nm, v_bc1);
|
| 198 |
+
let vh = _mm512_div_ps(nv, v_bc2);
|
| 199 |
+
let raiz = _mm512_sqrt_ps(vh); // IEEE-exata (não é rsqrt aproximado)
|
| 200 |
+
let denom = _mm512_add_ps(raiz, v_eps);
|
| 201 |
+
let quoc = _mm512_div_ps(mh, denom);
|
| 202 |
+
let decaimento = _mm512_mul_ps(v_wd, vw);
|
| 203 |
+
let ajuste = _mm512_add_ps(quoc, decaimento);
|
| 204 |
+
let passo = _mm512_mul_ps(v_lr, ajuste);
|
| 205 |
+
let novo = _mm512_sub_ps(vw, passo);
|
| 206 |
+
_mm512_storeu_ps(m.as_mut_ptr().add(j), nm);
|
| 207 |
+
_mm512_storeu_ps(v.as_mut_ptr().add(j), nv);
|
| 208 |
+
_mm512_storeu_ps(out.as_mut_ptr().add(j), novo);
|
| 209 |
+
j += 16;
|
| 210 |
+
}
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
// ---------------------------------------------------------------------------
|
| 214 |
+
// Kernel 2: escala elemento-a-elemento (clip de gradiente)
|
| 215 |
+
// ---------------------------------------------------------------------------
|
| 216 |
+
|
| 217 |
+
/// `x[i] *= f` para todo i — elemento-a-elemento ⇒ bit-a-bit idêntico ao
|
| 218 |
+
/// loop escalar do `AdamWManual::clipar` (uma única multiplicação por elemento).
|
| 219 |
+
pub fn escalar_slice(xs: &mut [f32], f: f32) {
|
| 220 |
+
let n = xs.len();
|
| 221 |
+
if tem_avx512() && n >= 64 {
|
| 222 |
+
CHAMADAS_AVX.fetch_add(1, Ordering::Relaxed);
|
| 223 |
+
unsafe { escalar_slice_avx(xs, f) };
|
| 224 |
+
let ini = n & !15;
|
| 225 |
+
for x in xs[ini..].iter_mut() {
|
| 226 |
+
*x *= f;
|
| 227 |
+
}
|
| 228 |
+
} else {
|
| 229 |
+
CHAMADAS_ESCALAR.fetch_add(1, Ordering::Relaxed);
|
| 230 |
+
for x in xs.iter_mut() {
|
| 231 |
+
*x *= f;
|
| 232 |
+
}
|
| 233 |
+
}
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
#[target_feature(enable = "avx512f,avx512dq")]
|
| 237 |
+
unsafe fn escalar_slice_avx(xs: &mut [f32], f: f32) {
|
| 238 |
+
use std::arch::x86_64::*;
|
| 239 |
+
let vf = _mm512_set1_ps(f);
|
| 240 |
+
let n = xs.len();
|
| 241 |
+
let mut j = 0usize;
|
| 242 |
+
while j + 16 <= n {
|
| 243 |
+
let x = _mm512_loadu_ps(xs.as_ptr().add(j));
|
| 244 |
+
_mm512_storeu_ps(xs.as_mut_ptr().add(j), _mm512_mul_ps(x, vf));
|
| 245 |
+
j += 16;
|
| 246 |
+
}
|
| 247 |
+
}
|
| 248 |
+
|
| 249 |
+
// ---------------------------------------------------------------------------
|
| 250 |
+
// Kernel 3: distâncias² 4D em layout SoA (crítica Qwen3.8 B.3)
|
| 251 |
+
// ---------------------------------------------------------------------------
|
| 252 |
+
|
| 253 |
+
/// Layout SoA column-major: `soa[j*c + i]` = coordenada j do protótipo i
|
| 254 |
+
/// (d = 4). Construído por [`soa_de_prototipos`].
|
| 255 |
+
///
|
| 256 |
+
/// **Teo X-2**: o termo (x_j − w_ij)² é computado por lane com sub/mul (sem
|
| 257 |
+
/// FMA) e a soma por protótipo percorre j = 0,1,2,3 na MESMA ordem do
|
| 258 |
+
/// `distancia2` escalar (fold esquerdo) ⇒ **bit-a-bit idêntico** ao caminho
|
| 259 |
+
/// `Vec<Vec<f64>>` da v0.8.0. A diferença é só a localidade: 1 buffer
|
| 260 |
+
/// contíguo (sem perseguição de ponteiro por protótipo) + 8 protótipos por
|
| 261 |
+
/// instrução no kernel vetorial.
|
| 262 |
+
pub fn soa_de_prototipos(ps: &[Vec<f64>]) -> (Vec<f64>, usize, usize) {
|
| 263 |
+
let c = ps.len();
|
| 264 |
+
let d = ps.first().map(|p| p.len()).unwrap_or(0);
|
| 265 |
+
let mut soa = vec![0.0f64; d * c];
|
| 266 |
+
for (i, p) in ps.iter().enumerate() {
|
| 267 |
+
for (j, x) in p.iter().enumerate() {
|
| 268 |
+
if j < d {
|
| 269 |
+
soa[j * c + i] = *x;
|
| 270 |
+
}
|
| 271 |
+
}
|
| 272 |
+
}
|
| 273 |
+
(soa, c, d)
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
/// Distâncias² de `x` a todos os `c` protótipos (modo EXATO — bit-a-bit).
|
| 277 |
+
/// `out` é preenchido com `distancia2(x, protótipo_i)` para todo i.
|
| 278 |
+
pub fn distancias4_exatas(soa: &[f64], c: usize, x: &[f64], out: &mut [f64]) {
|
| 279 |
+
debug_assert_eq!(soa.len(), 4 * c);
|
| 280 |
+
let usar_avx = tem_avx512() && c >= 64 && x.len() == 4;
|
| 281 |
+
if usar_avx {
|
| 282 |
+
CHAMADAS_AVX.fetch_add(1, Ordering::Relaxed);
|
| 283 |
+
unsafe { distancias4_exatas_avx(soa, c, x, out) };
|
| 284 |
+
let ini = c & !7;
|
| 285 |
+
distancias4_escalar(soa, c, x, out, ini);
|
| 286 |
+
} else {
|
| 287 |
+
CHAMADAS_ESCALAR.fetch_add(1, Ordering::Relaxed);
|
| 288 |
+
distancias4_escalar(soa, c, x, out, 0);
|
| 289 |
+
}
|
| 290 |
+
}
|
| 291 |
+
|
| 292 |
+
/// Trecho escalar — a MESMA ordem de soma do `distancia2` (fold esquerdo em j).
|
| 293 |
+
fn distancias4_escalar(soa: &[f64], c: usize, x: &[f64], out: &mut [f64], ini: usize) {
|
| 294 |
+
let d = if c == 0 { 0 } else { soa.len() / c };
|
| 295 |
+
for i in ini..c {
|
| 296 |
+
let mut s = 0.0f64;
|
| 297 |
+
for j in 0..d {
|
| 298 |
+
let dx = x[j] - soa[j * c + i];
|
| 299 |
+
s += dx * dx;
|
| 300 |
+
}
|
| 301 |
+
out[i] = s;
|
| 302 |
+
}
|
| 303 |
+
}
|
| 304 |
+
|
| 305 |
+
/// Redução horizontal exata: acumuladores por j são ARMAZENADOS e somados por
|
| 306 |
+
/// protótipo na ordem ((q0+q1)+q2)+q3 — a ordem do fold escalar.
|
| 307 |
+
#[target_feature(enable = "avx512f,avx512dq")]
|
| 308 |
+
unsafe fn distancias4_exatas_avx(soa: &[f64], c: usize, x: &[f64], out: &mut [f64]) {
|
| 309 |
+
use std::arch::x86_64::*;
|
| 310 |
+
let mut buf = [[0f64; 8]; 4];
|
| 311 |
+
let mut i = 0usize;
|
| 312 |
+
while i + 8 <= c {
|
| 313 |
+
let mut quads = [_mm512_setzero_pd(); 4];
|
| 314 |
+
for (j, q) in quads.iter_mut().enumerate() {
|
| 315 |
+
let wj = _mm512_loadu_pd(soa.as_ptr().add(j * c + i));
|
| 316 |
+
let xj = _mm512_set1_pd(x[j]);
|
| 317 |
+
let dx = _mm512_sub_pd(xj, wj);
|
| 318 |
+
*q = _mm512_mul_pd(dx, dx); // sem FMA
|
| 319 |
+
}
|
| 320 |
+
for j in 0..4 {
|
| 321 |
+
_mm512_storeu_pd(buf[j].as_mut_ptr(), quads[j]);
|
| 322 |
+
}
|
| 323 |
+
for k in 0..8 {
|
| 324 |
+
out[i + k] = ((buf[0][k] + buf[1][k]) + buf[2][k]) + buf[3][k];
|
| 325 |
+
}
|
| 326 |
+
i += 8;
|
| 327 |
+
}
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
/// Modo RÁPIDO: redução horizontal vetorizada `(q0+q1)+(q2+q3)` — ordem de
|
| 331 |
+
/// associação DIFERENTE do escalar ⇒ ε-diferença (≈ ulp), documentada;
|
| 332 |
+
/// usar apenas onde igualdade bit-a-bit não é garantia (ex.: sondagem).
|
| 333 |
+
pub fn distancias4_rapidas(soa: &[f64], c: usize, x: &[f64], out: &mut [f64]) {
|
| 334 |
+
debug_assert_eq!(soa.len(), 4 * c);
|
| 335 |
+
if tem_avx512() && c >= 64 && x.len() == 4 {
|
| 336 |
+
CHAMADAS_AVX.fetch_add(1, Ordering::Relaxed);
|
| 337 |
+
unsafe {
|
| 338 |
+
use std::arch::x86_64::*;
|
| 339 |
+
let mut i = 0usize;
|
| 340 |
+
while i + 8 <= c {
|
| 341 |
+
let mut quads = [_mm512_setzero_pd(); 4];
|
| 342 |
+
for (j, q) in quads.iter_mut().enumerate() {
|
| 343 |
+
let wj = _mm512_loadu_pd(soa.as_ptr().add(j * c + i));
|
| 344 |
+
let xj = _mm512_set1_pd(x[j]);
|
| 345 |
+
let dx = _mm512_sub_pd(xj, wj);
|
| 346 |
+
*q = _mm512_mul_pd(dx, dx);
|
| 347 |
+
}
|
| 348 |
+
let r = _mm512_add_pd(
|
| 349 |
+
_mm512_add_pd(quads[0], quads[1]),
|
| 350 |
+
_mm512_add_pd(quads[2], quads[3]),
|
| 351 |
+
);
|
| 352 |
+
_mm512_storeu_pd(out.as_mut_ptr().add(i), r);
|
| 353 |
+
i += 8;
|
| 354 |
+
}
|
| 355 |
+
};
|
| 356 |
+
let ini = c & !7;
|
| 357 |
+
distancias4_escalar(soa, c, x, out, ini);
|
| 358 |
+
} else {
|
| 359 |
+
CHAMADAS_ESCALAR.fetch_add(1, Ordering::Relaxed);
|
| 360 |
+
distancias4_escalar(soa, c, x, out, 0);
|
| 361 |
+
}
|
| 362 |
+
}
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
/// Layout ROW-MAJOR plano: `rm[i*d + j]` (protótipos contíguos, SEM
|
| 366 |
+
/// perseguição de ponteiros) — v0.9.0 (r-34 §6.4): a medição mostrou que o
|
| 367 |
+
/// SoA COLUMN-major (`soa[j*c+i]`) é MAIS LENTO que `Vec<Vec>` em d=4
|
| 368 |
+
/// (strides de c·8 B entre coordenadas quebram a localidade), enquanto o
|
| 369 |
+
/// row-major plano processa 2 protótipos por registrador ZMM (8 f64) com
|
| 370 |
+
/// redução horizontal EXATA por par (mesma ordem de soma do escalar).
|
| 371 |
+
pub fn rm_de_prototipos(ps: &[Vec<f64>]) -> (Vec<f64>, usize, usize) {
|
| 372 |
+
let c = ps.len();
|
| 373 |
+
let d = ps.first().map(|p| p.len()).unwrap_or(0);
|
| 374 |
+
let mut rm = vec![0.0f64; d * c];
|
| 375 |
+
for (i, p) in ps.iter().enumerate() {
|
| 376 |
+
for (j, x) in p.iter().enumerate() {
|
| 377 |
+
if j < d {
|
| 378 |
+
rm[i * d + j] = *x;
|
| 379 |
+
}
|
| 380 |
+
}
|
| 381 |
+
}
|
| 382 |
+
(rm, c, d)
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
/// Distâncias² d=4 sobre row-major plano — bit-a-bit idêntico ao escalar
|
| 386 |
+
/// (sub/mul por lane SEM FMA + redução por protótipo na ordem j=0..3).
|
| 387 |
+
pub fn distancias4_rm(rm: &[f64], c: usize, x: &[f64; 4], out: &mut [f64]) {
|
| 388 |
+
if tem_avx512() && c >= 64 {
|
| 389 |
+
CHAMADAS_AVX.fetch_add(1, Ordering::Relaxed);
|
| 390 |
+
unsafe { distancias4_rm_avx(rm, c, x, out) };
|
| 391 |
+
let ini = c & !1;
|
| 392 |
+
distancias4_rm_escalar(rm, c, x, out, ini);
|
| 393 |
+
} else {
|
| 394 |
+
CHAMADAS_ESCALAR.fetch_add(1, Ordering::Relaxed);
|
| 395 |
+
distancias4_rm_escalar(rm, c, x, out, 0);
|
| 396 |
+
}
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
fn distancias4_rm_escalar(rm: &[f64], c: usize, x: &[f64; 4], out: &mut [f64], ini: usize) {
|
| 400 |
+
for i in ini..c {
|
| 401 |
+
let b = &rm[i * 4..i * 4 + 4];
|
| 402 |
+
let t0 = x[0] - b[0];
|
| 403 |
+
let t1 = x[1] - b[1];
|
| 404 |
+
let t2 = x[2] - b[2];
|
| 405 |
+
let t3 = x[3] - b[3];
|
| 406 |
+
out[i] = ((t0 * t0 + t1 * t1) + t2 * t2) + t3 * t3;
|
| 407 |
+
}
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
/// 2 protótipos por ZMM: carga de 8 f64, x replicado [x0..x3, x0..x3],
|
| 411 |
+
/// sub/mul por lane; redução por PAR na ordem exata do escalar.
|
| 412 |
+
#[target_feature(enable = "avx512f,avx512dq")]
|
| 413 |
+
unsafe fn distancias4_rm_avx(rm: &[f64], c: usize, x: &[f64; 4], out: &mut [f64]) {
|
| 414 |
+
use std::arch::x86_64::*;
|
| 415 |
+
let xv = _mm512_setr_pd(x[0], x[1], x[2], x[3], x[0], x[1], x[2], x[3]);
|
| 416 |
+
let mut i = 0usize;
|
| 417 |
+
let mut par = [0f64; 8];
|
| 418 |
+
while i + 2 <= c {
|
| 419 |
+
let w = _mm512_loadu_pd(rm.as_ptr().add(i * 4));
|
| 420 |
+
let dif = _mm512_sub_pd(xv, w);
|
| 421 |
+
let q = _mm512_mul_pd(dif, dif); // lanes: [q0p, q1p, q2p, q3p, q0q, ..., q3q]
|
| 422 |
+
_mm512_storeu_pd(par.as_mut_ptr(), q);
|
| 423 |
+
let (p, r) = (i, i + 1);
|
| 424 |
+
// par JÁ contém os quadrados (q = dif*dif) — só SOMA, na ordem exata
|
| 425 |
+
out[p] = ((par[0] + par[1]) + par[2]) + par[3];
|
| 426 |
+
out[r] = ((par[4] + par[5]) + par[6]) + par[7];
|
| 427 |
+
i += 2;
|
| 428 |
+
}
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
/// Distâncias² row-major (despacho: d==4 → kernel AVX-512 2-protótipos/ZMM;
|
| 433 |
+
/// senão escalar genérico com a mesma ordem de soma) — preenche `buf`.
|
| 434 |
+
pub fn distancias_rm(rm: &[f64], c: usize, d: usize, x: &[f64], buf: &mut Vec<f64>) {
|
| 435 |
+
buf.clear();
|
| 436 |
+
buf.resize(c, 0.0);
|
| 437 |
+
if d == 4 && x.len() == 4 {
|
| 438 |
+
let x4 = [x[0], x[1], x[2], x[3]];
|
| 439 |
+
distancias4_rm(rm, c, &x4, buf);
|
| 440 |
+
} else {
|
| 441 |
+
CHAMADAS_ESCALAR.fetch_add(1, Ordering::Relaxed);
|
| 442 |
+
for i in 0..c {
|
| 443 |
+
let mut s = 0.0;
|
| 444 |
+
for j in 0..d {
|
| 445 |
+
let dx = x[j] - rm[i * d + j];
|
| 446 |
+
s += dx * dx;
|
| 447 |
+
}
|
| 448 |
+
buf[i] = s;
|
| 449 |
+
}
|
| 450 |
+
}
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
/// BMU sobre row-major plano — equivalente bit-a-bit a `melhor_indice(ps, x)`
|
| 454 |
+
/// (d=4; teste `distancias_bit_a_bit_rm`).
|
| 455 |
+
pub fn melhor_indice_rm_buf(rm: &[f64], c: usize, x: &[f64], buf: &mut Vec<f64>) -> usize {
|
| 456 |
+
if c == 0 {
|
| 457 |
+
return 0;
|
| 458 |
+
}
|
| 459 |
+
buf.clear();
|
| 460 |
+
buf.resize(c, 0.0);
|
| 461 |
+
if x.len() == 4 {
|
| 462 |
+
let x4 = [x[0], x[1], x[2], x[3]];
|
| 463 |
+
distancias4_rm(rm, c, &x4, buf);
|
| 464 |
+
} else {
|
| 465 |
+
let d = if c == 0 { 0 } else { rm.len() / c };
|
| 466 |
+
for i in 0..c {
|
| 467 |
+
let mut s = 0.0;
|
| 468 |
+
for j in 0..d {
|
| 469 |
+
let dx = x[j] - rm[i * d + j];
|
| 470 |
+
s += dx * dx;
|
| 471 |
+
}
|
| 472 |
+
buf[i] = s;
|
| 473 |
+
}
|
| 474 |
+
}
|
| 475 |
+
argmin_primeiro(buf)
|
| 476 |
+
}
|
| 477 |
+
|
| 478 |
+
/// argmin com desempate pelo MENOR índice — semântica idêntica à do
|
| 479 |
+
/// `melhor_indice` escalar (`dd < dm` estrito mantém o primeiro mínimo).
|
| 480 |
+
pub fn argmin_primeiro(xs: &[f64]) -> usize {
|
| 481 |
+
let mut melhor = 0usize;
|
| 482 |
+
let mut dm = f64::INFINITY;
|
| 483 |
+
for (i, x) in xs.iter().enumerate() {
|
| 484 |
+
if *x < dm {
|
| 485 |
+
dm = *x;
|
| 486 |
+
melhor = i;
|
| 487 |
+
}
|
| 488 |
+
}
|
| 489 |
+
melhor
|
| 490 |
+
}
|
| 491 |
+
|
| 492 |
+
/// Distâncias² para TODO d (despacho: d==4 → kernel AVX-512 exato; senão
|
| 493 |
+
/// escalar com a MESMA ordem de soma do `distancia2`) — preenche `buf`
|
| 494 |
+
/// (reutilizado entre chamadas: zero alocação por amostra).
|
| 495 |
+
pub fn distancias_soa(soa: &[f64], c: usize, d: usize, x: &[f64], buf: &mut Vec<f64>) {
|
| 496 |
+
buf.clear();
|
| 497 |
+
buf.resize(c, 0.0);
|
| 498 |
+
if d == 4 {
|
| 499 |
+
distancias4_exatas(soa, c, x, buf);
|
| 500 |
+
} else {
|
| 501 |
+
CHAMADAS_ESCALAR.fetch_add(1, Ordering::Relaxed);
|
| 502 |
+
distancias4_escalar(soa, c, x, buf, 0);
|
| 503 |
+
}
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
/// BMU sobre layout SoA — equivalente bit-a-bit a `melhor_indice(ps, x)`
|
| 507 |
+
/// (Teo X-2; teste `distancias_bit_a_bit`). Versão com buffer reutilizado.
|
| 508 |
+
pub fn melhor_indice_soa_buf(soa: &[f64], c: usize, d: usize, x: &[f64], buf: &mut Vec<f64>) -> usize {
|
| 509 |
+
if c == 0 {
|
| 510 |
+
return 0;
|
| 511 |
+
}
|
| 512 |
+
distancias_soa(soa, c, d, x, buf);
|
| 513 |
+
argmin_primeiro(buf)
|
| 514 |
+
}
|
| 515 |
+
|
| 516 |
+
/// BMU sobre layout SoA — equivalente bit-a-bit a `melhor_indice(ps, x)`
|
| 517 |
+
/// (Teo X-2; teste `distancias_bit_a_bit`).
|
| 518 |
+
pub fn melhor_indice_soa(soa: &[f64], c: usize, d: usize, x: &[f64]) -> usize {
|
| 519 |
+
let mut buf = Vec::with_capacity(c.max(1));
|
| 520 |
+
melhor_indice_soa_buf(soa, c, d, x, &mut buf)
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
// ---------------------------------------------------------------------------
|
| 524 |
+
// Buffer de heap com alinhamento GARANTIDO de 64 B (errata-2 da proposta)
|
| 525 |
+
// ---------------------------------------------------------------------------
|
| 526 |
+
|
| 527 |
+
/// Buffer f64 com alinhamento de heap GARANTIDO em 64 bytes (linha de cache /
|
| 528 |
+
// registrador ZMM). Esta é a forma CORRETA de obter o que a proposta
|
| 529 |
+
/// `#[repr(align(64))]` em struct-com-Vec prometia (e não entrega: isso
|
| 530 |
+
/// alinha a struct na pilha, não o buffer — ver teste `simd_enderecos`).
|
| 531 |
+
pub struct BufAlinhado64 {
|
| 532 |
+
ptr: *mut f64,
|
| 533 |
+
len: usize,
|
| 534 |
+
layout: Layout,
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
impl BufAlinhado64 {
|
| 538 |
+
/// Aloca `len` f64 zerados, alinhados a 64 B.
|
| 539 |
+
pub fn novo_zeros(len: usize) -> Self {
|
| 540 |
+
let len_b = (len * std::mem::size_of::<f64>()).max(1);
|
| 541 |
+
let layout = Layout::from_size_align(len_b, 64).expect("layout 64B inválido");
|
| 542 |
+
let ptr = unsafe { alloc(layout) };
|
| 543 |
+
assert!(!ptr.is_null(), "alocação alinhada falhou");
|
| 544 |
+
let buf = unsafe { std::slice::from_raw_parts_mut(ptr as *mut f64, len.max(1)) };
|
| 545 |
+
buf.fill(0.0);
|
| 546 |
+
Self { ptr: ptr as *mut f64, len: len.max(1), layout }
|
| 547 |
+
}
|
| 548 |
+
|
| 549 |
+
/// Cópia alinhada de uma fatia.
|
| 550 |
+
pub fn de_fatia(xs: &[f64]) -> Self {
|
| 551 |
+
let mut b = Self::novo_zeros(xs.len());
|
| 552 |
+
b.as_mut_slice().copy_from_slice(xs);
|
| 553 |
+
b
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
pub fn as_slice(&self) -> &[f64] {
|
| 557 |
+
if self.len == 0 {
|
| 558 |
+
&[]
|
| 559 |
+
} else {
|
| 560 |
+
unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
|
| 561 |
+
}
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
pub fn as_mut_slice(&mut self) -> &mut [f64] {
|
| 565 |
+
if self.len == 0 {
|
| 566 |
+
&mut []
|
| 567 |
+
} else {
|
| 568 |
+
unsafe { std::slice::from_raw_parts_mut(self.ptr, self.len) }
|
| 569 |
+
}
|
| 570 |
+
}
|
| 571 |
+
|
| 572 |
+
/// Endereço do buffer (para verificação de alinhamento em testes/bench).
|
| 573 |
+
pub fn endereco(&self) -> usize {
|
| 574 |
+
self.ptr as usize
|
| 575 |
+
}
|
| 576 |
+
}
|
| 577 |
+
|
| 578 |
+
impl Drop for BufAlinhado64 {
|
| 579 |
+
fn drop(&mut self) {
|
| 580 |
+
unsafe { dealloc(self.ptr as *mut u8, self.layout) };
|
| 581 |
+
}
|
| 582 |
+
}
|
| 583 |
+
|
| 584 |
+
impl std::fmt::Debug for BufAlinhado64 {
|
| 585 |
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
| 586 |
+
f.debug_struct("BufAlinhado64")
|
| 587 |
+
.field("len", &self.len)
|
| 588 |
+
.field("endereco", &format!("{:#x}", self.endereco()))
|
| 589 |
+
.finish()
|
| 590 |
+
}
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
// O ponteiro é ownership exclusivo (sem compartilhamento); Send/Sync seguros.
|
| 594 |
+
unsafe impl Send for BufAlinhado64 {}
|
| 595 |
+
unsafe impl Sync for BufAlinhado64 {}
|
| 596 |
+
|
| 597 |
+
// ---------------------------------------------------------------------------
|
| 598 |
+
// Testes — falsificam: FMA escondido, ordem de soma trocada, alinhamento falso
|
| 599 |
+
// ---------------------------------------------------------------------------
|
| 600 |
+
|
| 601 |
+
#[cfg(test)]
|
| 602 |
+
mod testes {
|
| 603 |
+
use super::*;
|
| 604 |
+
|
| 605 |
+
fn rng_vec(n: usize, semente: u64) -> Vec<f32> {
|
| 606 |
+
let mut rng = crate::rng::RngDeterministico::novo(semente);
|
| 607 |
+
(0..n).map(|_| rng.normal(0.0, 1.0) as f32).collect()
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
+
/// Teo X-1 (falsificação): qualquer FMA/rsqrt/ordem diferente quebra os bits.
|
| 611 |
+
#[test]
|
| 612 |
+
fn adamw_bit_a_bit() {
|
| 613 |
+
let n = 1000usize;
|
| 614 |
+
let w = rng_vec(n, 1);
|
| 615 |
+
let g = rng_vec(n, 2);
|
| 616 |
+
let mut m1 = rng_vec(n, 3);
|
| 617 |
+
let mut v1 = rng_vec(n, 4);
|
| 618 |
+
let mut m2 = m1.clone();
|
| 619 |
+
let mut v2 = v1.clone();
|
| 620 |
+
let mut o1 = vec![0f32; n];
|
| 621 |
+
let mut o2 = vec![0f32; n];
|
| 622 |
+
// referência: loop escalar IDÊNTICO ao que estava em treino.rs v0.8.0
|
| 623 |
+
// (atenção: `(1.0 - b1 as f32)` — subtração em f32 pós-cast)
|
| 624 |
+
let (lr, b1, b2, eps, wd, bc1, bc2) = (3e-4f64, 0.9, 0.999, 1e-8, 0.01, 0.91, 0.001);
|
| 625 |
+
let (lrf, b1f, b2f, epsf, wdf, bc1f, bc2f) = (
|
| 626 |
+
lr as f32,
|
| 627 |
+
b1 as f32,
|
| 628 |
+
b2 as f32,
|
| 629 |
+
eps as f32,
|
| 630 |
+
wd as f32,
|
| 631 |
+
bc1 as f32,
|
| 632 |
+
bc2 as f32,
|
| 633 |
+
);
|
| 634 |
+
for j in 0..n {
|
| 635 |
+
m1[j] = b1f * m1[j] + (1.0f32 - b1f) * g[j];
|
| 636 |
+
v1[j] = b2f * v1[j] + (1.0f32 - b2f) * g[j] * g[j];
|
| 637 |
+
let mh = m1[j] / bc1f;
|
| 638 |
+
let vh = v1[j] / bc2f;
|
| 639 |
+
o1[j] = w[j] - lrf * (mh / (vh.sqrt() + epsf) + wdf * w[j]);
|
| 640 |
+
}
|
| 641 |
+
// caminho SIMD (com cauda escalar interna)
|
| 642 |
+
passo_adamw(&w, &g, &mut m2, &mut v2, &mut o2, lr, b1, b2, eps, wd, bc1, bc2);
|
| 643 |
+
for j in 0..n {
|
| 644 |
+
assert_eq!(o1[j].to_bits(), o2[j].to_bits(), "saída divergiu em {j}");
|
| 645 |
+
assert_eq!(m1[j].to_bits(), m2[j].to_bits(), "m divergiu em {j}");
|
| 646 |
+
assert_eq!(v1[j].to_bits(), v2[j].to_bits(), "v divergiu em {j}");
|
| 647 |
+
}
|
| 648 |
+
}
|
| 649 |
+
|
| 650 |
+
#[test]
|
| 651 |
+
fn escalar_bit_a_bit() {
|
| 652 |
+
let n = 500usize;
|
| 653 |
+
let mut a = rng_vec(n, 5);
|
| 654 |
+
let mut b = a.clone();
|
| 655 |
+
let f = 0.37f32;
|
| 656 |
+
escalar_slice(&mut a, f);
|
| 657 |
+
for x in b.iter_mut() {
|
| 658 |
+
*x *= f;
|
| 659 |
+
}
|
| 660 |
+
for j in 0..n {
|
| 661 |
+
assert_eq!(a[j].to_bits(), b[j].to_bits());
|
| 662 |
+
}
|
| 663 |
+
}
|
| 664 |
+
|
| 665 |
+
/// Teo X-2 (falsificação): ordem de soma ou FMA no kernel SoA quebra bits.
|
| 666 |
+
#[test]
|
| 667 |
+
fn distancias_bit_a_bit() {
|
| 668 |
+
let (c, d) = (300usize, 4usize);
|
| 669 |
+
let mut rng = crate::rng::RngDeterministico::novo(11);
|
| 670 |
+
let ps: Vec<Vec<f64>> = (0..c)
|
| 671 |
+
.map(|_| (0..d).map(|_| rng.normal(0.0, 2.0)).collect())
|
| 672 |
+
.collect();
|
| 673 |
+
let x: Vec<f64> = (0..d).map(|_| rng.normal(0.0, 2.0)).collect();
|
| 674 |
+
let (soa, c2, d2) = soa_de_prototipos(&ps);
|
| 675 |
+
assert_eq!((c2, d2), (c, d));
|
| 676 |
+
// melhor_indice escalar da v0.8.0 (réplica)
|
| 677 |
+
let dist_esc = |a: &[f64], b: &[f64]| -> f64 {
|
| 678 |
+
a.iter().zip(b).map(|(p, q)| (p - q) * (p - q)).sum()
|
| 679 |
+
};
|
| 680 |
+
let mut melhor = 0usize;
|
| 681 |
+
let mut dm = f64::INFINITY;
|
| 682 |
+
for (i, p) in ps.iter().enumerate() {
|
| 683 |
+
let dd = dist_esc(&x, p);
|
| 684 |
+
if dd < dm {
|
| 685 |
+
dm = dd;
|
| 686 |
+
melhor = i;
|
| 687 |
+
}
|
| 688 |
+
}
|
| 689 |
+
// caminho SoA (AVX-512 quando disponível; cauda escalar)
|
| 690 |
+
let obtido = melhor_indice_soa(&soa, c, d, &x);
|
| 691 |
+
assert_eq!(obtido, melhor, "BMU divergiu");
|
| 692 |
+
// distâncias completas bit-a-bit
|
| 693 |
+
let mut out = vec![0.0f64; c];
|
| 694 |
+
distancias4_exatas(&soa, c, &x, &mut out);
|
| 695 |
+
for (i, p) in ps.iter().enumerate() {
|
| 696 |
+
assert_eq!(
|
| 697 |
+
out[i].to_bits(),
|
| 698 |
+
dist_esc(&x, p).to_bits(),
|
| 699 |
+
"distância divergiu no protótipo {i}"
|
| 700 |
+
);
|
| 701 |
+
}
|
| 702 |
+
// modo rápido: ε-próximo e mesmo BMU (ordem de associação diferente)
|
| 703 |
+
let mut rap = vec![0.0f64; c];
|
| 704 |
+
distancias4_rapidas(&soa, c, &x, &mut rap);
|
| 705 |
+
for i in 0..c {
|
| 706 |
+
let esc = dist_esc(&x, &ps[i]);
|
| 707 |
+
assert!(
|
| 708 |
+
(rap[i] - esc).abs() <= 1e-9 * (1.0 + esc.abs()),
|
| 709 |
+
"modo rápido longe demais: {} vs {}",
|
| 710 |
+
rap[i],
|
| 711 |
+
esc
|
| 712 |
+
);
|
| 713 |
+
}
|
| 714 |
+
assert_eq!(argmin_primeiro(&rap), melhor);
|
| 715 |
+
}
|
| 716 |
+
|
| 717 |
+
|
| 718 |
+
/// Teo X-2' (row-major): o kernel de 2-protótipos-por-ZMM preserva bits
|
| 719 |
+
/// contra o escalar `distancia2`/`melhor_indice`.
|
| 720 |
+
#[test]
|
| 721 |
+
fn distancias_bit_a_bit_rm() {
|
| 722 |
+
let (c, d) = (500usize, 4usize);
|
| 723 |
+
let mut rng = crate::rng::RngDeterministico::novo(19);
|
| 724 |
+
let ps: Vec<Vec<f64>> = (0..c)
|
| 725 |
+
.map(|_| (0..d).map(|_| rng.normal(0.0, 2.0)).collect())
|
| 726 |
+
.collect();
|
| 727 |
+
let x: Vec<f64> = (0..d).map(|_| rng.normal(0.0, 2.0)).collect();
|
| 728 |
+
let (rm, c2, d2) = rm_de_prototipos(&ps);
|
| 729 |
+
assert_eq!((c2, d2), (c, d));
|
| 730 |
+
let dist_esc = |a: &[f64], b: &[f64]| -> f64 {
|
| 731 |
+
a.iter().zip(b).map(|(p, q)| (p - q) * (p - q)).sum()
|
| 732 |
+
};
|
| 733 |
+
let mut melhor = 0usize;
|
| 734 |
+
let mut dm = f64::INFINITY;
|
| 735 |
+
for (i, p) in ps.iter().enumerate() {
|
| 736 |
+
let dd = dist_esc(&x, p);
|
| 737 |
+
if dd < dm {
|
| 738 |
+
dm = dd;
|
| 739 |
+
melhor = i;
|
| 740 |
+
}
|
| 741 |
+
}
|
| 742 |
+
let mut buf = Vec::with_capacity(c);
|
| 743 |
+
let obtido = melhor_indice_rm_buf(&rm, c, &x, &mut buf);
|
| 744 |
+
assert_eq!(obtido, melhor, "BMU rm divergiu");
|
| 745 |
+
for (i, p) in ps.iter().enumerate() {
|
| 746 |
+
assert_eq!(buf[i].to_bits(), dist_esc(&x, p).to_bits(), "rm dist divergiu em {i}");
|
| 747 |
+
}
|
| 748 |
+
}
|
| 749 |
+
|
| 750 |
+
/// Errata-2 empirica: o buffer dedicado É alinhado a 64; `Vec` comum NÃO
|
| 751 |
+
/// tem essa garantia (apenas o alinhamento do tipo/allocador).
|
| 752 |
+
#[test]
|
| 753 |
+
fn simd_enderecos() {
|
| 754 |
+
for len in [1usize, 7, 64, 257, 1000, 4096] {
|
| 755 |
+
let b = BufAlinhado64::novo_zeros(len);
|
| 756 |
+
assert_eq!(b.endereco() % 64, 0, "alinhamento 64B violado (len={len})");
|
| 757 |
+
assert_eq!(b.as_slice().len(), len.max(1));
|
| 758 |
+
}
|
| 759 |
+
// cópia preserva conteúdo e alinhamento
|
| 760 |
+
let orig: Vec<f64> = (0..100).map(|i| i as f64 * 0.5).collect();
|
| 761 |
+
let b = BufAlinhado64::de_fatia(&orig);
|
| 762 |
+
assert_eq!(b.endereco() % 64, 0);
|
| 763 |
+
assert_eq!(b.as_slice(), orig.as_slice());
|
| 764 |
+
// Vec<f64> do alocador global: SEM garantia de 64B (documenta a errata;
|
| 765 |
+
// não afirmamos que sempre viola — afirmamos que NÃO é garantido)
|
| 766 |
+
let mut violacoes = 0;
|
| 767 |
+
for _ in 0..32 {
|
| 768 |
+
let v: Vec<f64> = (0..1000).map(|i| i as f64).collect();
|
| 769 |
+
if v.as_ptr() as usize % 64 != 0 {
|
| 770 |
+
violacoes += 1;
|
| 771 |
+
}
|
| 772 |
+
}
|
| 773 |
+
// com glibc, alocações grandes frequentemente caem em páginas alinhadas
|
| 774 |
+
// — o ponto é a GARANTIA, não a frequência; apenas registramos.
|
| 775 |
+
eprintln!("[simd_enderecos] Vec<f64> não-alinhado em {violacoes}/32 alocações");
|
| 776 |
+
}
|
| 777 |
+
|
| 778 |
+
#[test]
|
| 779 |
+
fn despacho_conta_chamadas() {
|
| 780 |
+
let antes = relatorio();
|
| 781 |
+
let b = BufAlinhado64::novo_zeros(128);
|
| 782 |
+
let x = [0.1f64, 0.2, 0.3, 0.4];
|
| 783 |
+
let mut out = vec![0.0f64; 128];
|
| 784 |
+
distancias4_exatas(b.as_slice(), 128, &x, &mut out);
|
| 785 |
+
let depois = relatorio();
|
| 786 |
+
let n0 = antes["chamadas_avx"].as_u64().unwrap_or(0)
|
| 787 |
+
+ antes["chamadas_escalar"].as_u64().unwrap_or(0);
|
| 788 |
+
let n1 = depois["chamadas_avx"].as_u64().unwrap_or(0)
|
| 789 |
+
+ depois["chamadas_escalar"].as_u64().unwrap_or(0);
|
| 790 |
+
assert!(n1 > n0, "contadores de despacho não incrementaram");
|
| 791 |
+
}
|
| 792 |
+
}
|
src/som4d.rs
CHANGED
|
@@ -145,37 +145,28 @@ impl Som4DTreinado {
|
|
| 145 |
|
| 146 |
/// EQ (erro de quantização médio) e TE (erro topográfico: fração de
|
| 147 |
/// amostras cujo 1º e 2º BMUs NÃO são vizinhos na grade 4D).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
pub fn metricas(&self, dados: &[Vec<f64>]) -> (f64, f64) {
|
| 149 |
let n = dados.len();
|
| 150 |
if n == 0 {
|
| 151 |
return (0.0, 0.0);
|
| 152 |
}
|
|
|
|
|
|
|
| 153 |
let mut eq = 0.0;
|
| 154 |
let mut te = 0usize;
|
| 155 |
for x in dados {
|
| 156 |
-
|
| 157 |
-
let (m1, d1) =
|
| 158 |
-
|
| 159 |
-
let mut d2m = f64::INFINITY;
|
| 160 |
-
for (i, p) in self.prototipos.iter().enumerate() {
|
| 161 |
-
if i == m1 {
|
| 162 |
-
continue;
|
| 163 |
-
}
|
| 164 |
-
let d2 = distancia2(x, p);
|
| 165 |
-
if d2 < d2m {
|
| 166 |
-
d2m = d2;
|
| 167 |
-
m2 = i;
|
| 168 |
-
}
|
| 169 |
-
}
|
| 170 |
-
eq += d1.sqrt();
|
| 171 |
-
if m2 != usize::MAX {
|
| 172 |
let a = self.coords(m1);
|
| 173 |
let b = self.coords(m2);
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
false => false,
|
| 177 |
-
};
|
| 178 |
-
// TE usa a topologia declarada; guardamos Moore no campo
|
| 179 |
if !viz {
|
| 180 |
te += 1;
|
| 181 |
}
|
|
@@ -265,18 +256,24 @@ pub fn treinar_som4d(dados: &Tensor, cfg: &CfgSom4D, _dev: &Device) -> candle_co
|
|
| 265 |
if !cfg.calcular_metricas {
|
| 266 |
return (f64::NAN, f64::NAN);
|
| 267 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 268 |
let mut eq = 0.0;
|
| 269 |
let mut te = 0usize;
|
| 270 |
for x in &xs {
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
|
|
|
| 280 |
}
|
| 281 |
}
|
| 282 |
(eq / n as f64, te as f64 / n as f64)
|
|
@@ -292,7 +289,12 @@ pub fn treinar_som4d(dados: &Tensor, cfg: &CfgSom4D, _dev: &Device) -> candle_co
|
|
| 292 |
let eta = cfg.eta0 + (cfg.eta1 - cfg.eta0) * frac;
|
| 293 |
let sigma2 = 2.0 * sigma * sigma;
|
| 294 |
|
| 295 |
-
// atribuição (BMU) — congelada nesta época
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 296 |
let bmu_idx: Vec<usize> = xs.iter().map(|x| melhor_indice(&prototipos, x)).collect();
|
| 297 |
|
| 298 |
// acumulação determinística
|
|
@@ -397,6 +399,61 @@ fn dois_melhores(ps: &[Vec<f64>], x: &[f64]) -> (usize, usize) {
|
|
| 397 |
(i1, i2)
|
| 398 |
}
|
| 399 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
// ---------------------------------------------------------------------------
|
| 401 |
// T-SOM 4D — Time SOM com esquecimento exponencial
|
| 402 |
// ---------------------------------------------------------------------------
|
|
@@ -536,7 +593,33 @@ impl Som4DTreinado {
|
|
| 536 |
// GNG — Growing Neural Gas (Fritzke)
|
| 537 |
// ---------------------------------------------------------------------------
|
| 538 |
|
| 539 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 540 |
pub struct CfgGng {
|
| 541 |
/// passo do vencedor ε_b.
|
| 542 |
pub eps_b: f64,
|
|
@@ -555,6 +638,9 @@ pub struct CfgGng {
|
|
| 555 |
/// nº de passos de treino.
|
| 556 |
pub passos: usize,
|
| 557 |
pub semente: u64,
|
|
|
|
|
|
|
|
|
|
| 558 |
}
|
| 559 |
|
| 560 |
impl Default for CfgGng {
|
|
@@ -569,6 +655,7 @@ impl Default for CfgGng {
|
|
| 569 |
max_vertices: 60,
|
| 570 |
passos: 4000,
|
| 571 |
semente: 42,
|
|
|
|
| 572 |
}
|
| 573 |
}
|
| 574 |
}
|
|
@@ -581,6 +668,9 @@ pub struct GngTreinado {
|
|
| 581 |
pub eq_medio: f64,
|
| 582 |
/// nº de inserções realizadas.
|
| 583 |
pub insercoes: usize,
|
|
|
|
|
|
|
|
|
|
| 584 |
}
|
| 585 |
|
| 586 |
impl GngTreinado {
|
|
@@ -626,13 +716,22 @@ pub fn treinar_gng(dados: &Tensor, cfg: &CfgGng) -> candle_core::Result<GngTrein
|
|
| 626 |
let mut arestas: Vec<((usize, usize), u32)> = vec![((0, 1), 0)];
|
| 627 |
let mut erro = vec![0.0f64; 2];
|
| 628 |
let mut insercoes = 0usize;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 629 |
|
| 630 |
for passo in 0..cfg.passos {
|
| 631 |
let x = &xs[rng.inteiro(n)];
|
| 632 |
// dois mais próximos
|
| 633 |
let (v1, v2) = dois_melhores(&vertices, x);
|
| 634 |
// erro acumulado do vencedor
|
| 635 |
-
|
|
|
|
|
|
|
|
|
|
| 636 |
// move o vencedor e seus vizinhos topológicos
|
| 637 |
for j in 0..d {
|
| 638 |
vertices[v1][j] += cfg.eps_b * (x[j] - vertices[v1][j]);
|
|
@@ -670,8 +769,40 @@ pub fn treinar_gng(dados: &Tensor, cfg: &CfgGng) -> candle_core::Result<GngTrein
|
|
| 670 |
*e *= cfg.decaimento_erro;
|
| 671 |
}
|
| 672 |
|
| 673 |
-
// inserção a cada λ passos
|
|
|
|
|
|
|
| 674 |
if passo % cfg.lambda == cfg.lambda - 1 && vertices.len() < cfg.max_vertices {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 675 |
// vértice de maior erro q com vizinho de maior erro
|
| 676 |
let q = erro
|
| 677 |
.iter()
|
|
@@ -702,6 +833,7 @@ pub fn treinar_gng(dados: &Tensor, cfg: &CfgGng) -> candle_core::Result<GngTrein
|
|
| 702 |
arestas.push(((f.min(novo), f.max(novo)), 0));
|
| 703 |
insercoes += 1;
|
| 704 |
}
|
|
|
|
| 705 |
}
|
| 706 |
// vértices isolados (sem arestas) permanecem — GNG clássico não remove
|
| 707 |
}
|
|
@@ -718,6 +850,7 @@ pub fn treinar_gng(dados: &Tensor, cfg: &CfgGng) -> candle_core::Result<GngTrein
|
|
| 718 |
erro,
|
| 719 |
eq_medio: eq / n as f64,
|
| 720 |
insercoes,
|
|
|
|
| 721 |
})
|
| 722 |
}
|
| 723 |
|
|
|
|
| 145 |
|
| 146 |
/// EQ (erro de quantização médio) e TE (erro topográfico: fração de
|
| 147 |
/// amostras cujo 1º e 2º BMUs NÃO são vizinhos na grade 4D).
|
| 148 |
+
///
|
| 149 |
+
/// v0.9.0: caminho SoA (crítica Qwen3.8 B.3) — 1 buffer contíguo + kernel
|
| 150 |
+
/// AVX-512 exato (d=4) + `dois_menores` sobre as distâncias JÁ computadas
|
| 151 |
+
/// (a v0.8.0 fazia 2 varreduras completas por amostra). Distâncias
|
| 152 |
+
/// bit-a-bit idênticas ⇒ EQ/TE bit-a-bit idênticos (Teo X-2).
|
| 153 |
pub fn metricas(&self, dados: &[Vec<f64>]) -> (f64, f64) {
|
| 154 |
let n = dados.len();
|
| 155 |
if n == 0 {
|
| 156 |
return (0.0, 0.0);
|
| 157 |
}
|
| 158 |
+
let (soa, c_soa, d_soa) = crate::simd::soa_de_prototipos(&self.prototipos);
|
| 159 |
+
let mut buf: Vec<f64> = Vec::with_capacity(c_soa);
|
| 160 |
let mut eq = 0.0;
|
| 161 |
let mut te = 0usize;
|
| 162 |
for x in dados {
|
| 163 |
+
crate::simd::distancias_soa(&soa, c_soa, d_soa, x, &mut buf);
|
| 164 |
+
if let Some((m1, m2, d1)) = dois_menores(&buf) {
|
| 165 |
+
eq += d1.sqrt();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
let a = self.coords(m1);
|
| 167 |
let b = self.coords(m2);
|
| 168 |
+
// comportamento v0.8.0 preservado: vizinhança de Moore
|
| 169 |
+
let viz = self_topologia_vizinha(&a, &b);
|
|
|
|
|
|
|
|
|
|
| 170 |
if !viz {
|
| 171 |
te += 1;
|
| 172 |
}
|
|
|
|
| 256 |
if !cfg.calcular_metricas {
|
| 257 |
return (f64::NAN, f64::NAN);
|
| 258 |
}
|
| 259 |
+
// v0.9.0: SoA + kernel exato + dois_menores sobre distâncias já
|
| 260 |
+
// computadas (1 varredura por amostra em vez de 2) — valores
|
| 261 |
+
// bit-a-bit idênticos à v0.8.0 (Teo X-2)
|
| 262 |
+
let (soa, c_soa, d_soa) = crate::simd::soa_de_prototipos(ps);
|
| 263 |
+
let mut buf: Vec<f64> = Vec::with_capacity(c_soa);
|
| 264 |
let mut eq = 0.0;
|
| 265 |
let mut te = 0usize;
|
| 266 |
for x in &xs {
|
| 267 |
+
crate::simd::distancias_soa(&soa, c_soa, d_soa, x, &mut buf);
|
| 268 |
+
if let Some((i1, i2, d1)) = dois_menores(&buf) {
|
| 269 |
+
eq += d1.sqrt();
|
| 270 |
+
let viz = match cfg.topologia {
|
| 271 |
+
Topologia4D::Moore => self_topologia_vizinha(&coords[i1], &coords[i2]),
|
| 272 |
+
Topologia4D::VonNeumann => vizinhas_vn(&coords[i1], &coords[i2]),
|
| 273 |
+
};
|
| 274 |
+
if !viz {
|
| 275 |
+
te += 1;
|
| 276 |
+
}
|
| 277 |
}
|
| 278 |
}
|
| 279 |
(eq / n as f64, te as f64 / n as f64)
|
|
|
|
| 289 |
let eta = cfg.eta0 + (cfg.eta1 - cfg.eta0) * frac;
|
| 290 |
let sigma2 = 2.0 * sigma * sigma;
|
| 291 |
|
| 292 |
+
// atribuição (BMU) — congelada nesta época.
|
| 293 |
+
// v0.9.0 (medição honesta, r-34 §6.4): o caminho escalar contíguo
|
| 294 |
+
// VENCE todos os kernels SIMD p/ argmin-only em d=4 (não materializa
|
| 295 |
+
// o vetor de distâncias; a busca é limitada por memória dos PESOS) —
|
| 296 |
+
// mantido o `melhor_indice` original, agora documentado como ótimo
|
| 297 |
+
// medido, não como default por inércia.
|
| 298 |
let bmu_idx: Vec<usize> = xs.iter().map(|x| melhor_indice(&prototipos, x)).collect();
|
| 299 |
|
| 300 |
// acumulação determinística
|
|
|
|
| 399 |
(i1, i2)
|
| 400 |
}
|
| 401 |
|
| 402 |
+
/// Dois menores de um vetor de distâncias JÁ COMPUTADAS — mesmo algoritmo
|
| 403 |
+
/// (e mesma semântica de empate) do `dois_melhores`, evitando recomputar as
|
| 404 |
+
/// distâncias duas vezes por amostra (v0.8.0 computava `melhor_indice` E
|
| 405 |
+
/// `dois_melhores` separadamente = 2 varreduras completas).
|
| 406 |
+
fn dois_menores(dists: &[f64]) -> Option<(usize, usize, f64)> {
|
| 407 |
+
if dists.len() < 2 {
|
| 408 |
+
return None;
|
| 409 |
+
}
|
| 410 |
+
let (mut i1, mut d1) = (0usize, dists[0]);
|
| 411 |
+
let (mut i2, mut d2) = (1usize, dists[1]);
|
| 412 |
+
if d2 < d1 {
|
| 413 |
+
std::mem::swap(&mut i1, &mut i2);
|
| 414 |
+
std::mem::swap(&mut d1, &mut d2);
|
| 415 |
+
}
|
| 416 |
+
for (i, &dd) in dists.iter().enumerate().skip(2) {
|
| 417 |
+
if dd < d1 {
|
| 418 |
+
i2 = i1;
|
| 419 |
+
d2 = d1;
|
| 420 |
+
i1 = i;
|
| 421 |
+
d1 = dd;
|
| 422 |
+
} else if dd < d2 {
|
| 423 |
+
i2 = i;
|
| 424 |
+
d2 = dd;
|
| 425 |
+
}
|
| 426 |
+
}
|
| 427 |
+
Some((i1, i2, d1))
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
/// Entropia de Shannon do vetor de erros, NORMALIZADA por ln(V) ∈ [0,1]
|
| 431 |
+
/// (v0.9.0, crítica Qwen3.8 B.1 — r-34 §6): H=0 ⇒ todo o erro numa célula;
|
| 432 |
+
/// H=1 ⇒ erro uniformemente espalhado. Sem sinal (Σe=0) devolve 1 (nada
|
| 433 |
+
/// concentrado — não há o que aliviar com inserção local).
|
| 434 |
+
fn entropia_normalizada(erro: &[f64]) -> f64 {
|
| 435 |
+
let soma: f64 = erro.iter().sum();
|
| 436 |
+
if soma <= 0.0 || erro.len() < 2 {
|
| 437 |
+
return 1.0;
|
| 438 |
+
}
|
| 439 |
+
let ln_v = (erro.len() as f64).ln();
|
| 440 |
+
if ln_v <= 0.0 {
|
| 441 |
+
return 1.0;
|
| 442 |
+
}
|
| 443 |
+
let h: f64 = erro
|
| 444 |
+
.iter()
|
| 445 |
+
.map(|&e| {
|
| 446 |
+
let p = e / soma;
|
| 447 |
+
if p > 0.0 {
|
| 448 |
+
-p * p.ln()
|
| 449 |
+
} else {
|
| 450 |
+
0.0
|
| 451 |
+
}
|
| 452 |
+
})
|
| 453 |
+
.sum();
|
| 454 |
+
(h / ln_v).clamp(0.0, 1.0)
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
// ---------------------------------------------------------------------------
|
| 458 |
// T-SOM 4D — Time SOM com esquecimento exponencial
|
| 459 |
// ---------------------------------------------------------------------------
|
|
|
|
| 593 |
// GNG — Growing Neural Gas (Fritzke)
|
| 594 |
// ---------------------------------------------------------------------------
|
| 595 |
|
| 596 |
+
/// Critério de inserção de vértices do GNG (v0.9.0, crítica Qwen3.8 B.1;
|
| 597 |
+
/// prova T-G1 em r-34 §6).
|
| 598 |
+
#[derive(Debug, Clone, Copy, PartialEq)]
|
| 599 |
+
pub enum CriterioInsercao {
|
| 600 |
+
/// Padrão desde a v0.5.0: a cada λ passos insere no vértice de maior
|
| 601 |
+
/// erro — comportamento bit-a-bit preservado (é o Default).
|
| 602 |
+
ErroMaximo,
|
| 603 |
+
/// Entropia concentrada: insere APENAS quando o erro acumulado está
|
| 604 |
+
/// CONCENTRADO — H(e)/ln(V) ≤ teto. T-G1: com Σe fixo, e_max ≥
|
| 605 |
+
/// Σe·V^(−H/ln V) ≥ Σe·V^(−teto) — o portão de entropia garante o piso
|
| 606 |
+
/// de alívio da inserção (com erro disperso, e_max pode ser tão baixo
|
| 607 |
+
/// quanto Σe/V e a inserção local rende pouco).
|
| 608 |
+
///
|
| 609 |
+
/// **AVISO honesto (medição sintética, r-34 §6.3)**: como GATILHO ÚNICO,
|
| 610 |
+
/// este critério SOFRE FOME em dados multimodais simétricos (2 clusters
|
| 611 |
+
/// iguais ⇒ H=1 sempre ⇒ 0 inserções, EQ 0,98 vs 0,45 do padrão): a
|
| 612 |
+
/// entropia diz ONDE há concentração, mas o SE há necessidade é o NÍVEL
|
| 613 |
+
/// absoluto (QE). Para esse caso use `EntropiaComPiso`.
|
| 614 |
+
EntropiaConcentrada { teto: f64 },
|
| 615 |
+
/// Entropia com piso de QE (v0.9.0, correção da fome acima): insere se
|
| 616 |
+
/// (H ≤ teto) OU (QE_atual > frac_qe · QE_inicial) — a concentração
|
| 617 |
+
/// orienta, o nível absoluto NUNCA deixa faltar capacidade. T-G1':
|
| 618 |
+
/// a condição disjuntiva preserva o piso de alívio de T-G1 no ramo de
|
| 619 |
+
/// entropia e adiciona o piso absoluto no ramo de nível.
|
| 620 |
+
EntropiaComPiso { teto: f64, frac_qe: f64 },
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
pub struct CfgGng {
|
| 624 |
/// passo do vencedor ε_b.
|
| 625 |
pub eps_b: f64,
|
|
|
|
| 638 |
/// nº de passos de treino.
|
| 639 |
pub passos: usize,
|
| 640 |
pub semente: u64,
|
| 641 |
+
/// Critério de inserção (v0.9.0). Default = ErroMaximo (compatível
|
| 642 |
+
/// bit-a-bit com a v0.5.0–v0.8.0).
|
| 643 |
+
pub criterio: CriterioInsercao,
|
| 644 |
}
|
| 645 |
|
| 646 |
impl Default for CfgGng {
|
|
|
|
| 655 |
max_vertices: 60,
|
| 656 |
passos: 4000,
|
| 657 |
semente: 42,
|
| 658 |
+
criterio: CriterioInsercao::ErroMaximo,
|
| 659 |
}
|
| 660 |
}
|
| 661 |
}
|
|
|
|
| 668 |
pub eq_medio: f64,
|
| 669 |
/// nº de inserções realizadas.
|
| 670 |
pub insercoes: usize,
|
| 671 |
+
/// nº de inserções BLOQUEADAS pelo critério de entropia (v0.9.0) —
|
| 672 |
+
/// telemetria do portão T-G1.
|
| 673 |
+
pub insercoes_bloqueadas: usize,
|
| 674 |
}
|
| 675 |
|
| 676 |
impl GngTreinado {
|
|
|
|
| 716 |
let mut arestas: Vec<((usize, usize), u32)> = vec![((0, 1), 0)];
|
| 717 |
let mut erro = vec![0.0f64; 2];
|
| 718 |
let mut insercoes = 0usize;
|
| 719 |
+
let mut insercoes_bloqueadas = 0usize;
|
| 720 |
+
// janelas de QE (para EntropiaComPiso): média de d² por janela de λ passos
|
| 721 |
+
let mut soma_d2_janela = 0.0f64;
|
| 722 |
+
let mut conta_janela = 0usize;
|
| 723 |
+
let mut qe_janela: Option<f64> = None; // QE da 1ª janela (referência)
|
| 724 |
+
let mut qe_atual: Option<f64> = None;
|
| 725 |
|
| 726 |
for passo in 0..cfg.passos {
|
| 727 |
let x = &xs[rng.inteiro(n)];
|
| 728 |
// dois mais próximos
|
| 729 |
let (v1, v2) = dois_melhores(&vertices, x);
|
| 730 |
// erro acumulado do vencedor
|
| 731 |
+
let d2 = distancia2(x, &vertices[v1]);
|
| 732 |
+
erro[v1] += d2;
|
| 733 |
+
soma_d2_janela += d2;
|
| 734 |
+
conta_janela += 1;
|
| 735 |
// move o vencedor e seus vizinhos topológicos
|
| 736 |
for j in 0..d {
|
| 737 |
vertices[v1][j] += cfg.eps_b * (x[j] - vertices[v1][j]);
|
|
|
|
| 769 |
*e *= cfg.decaimento_erro;
|
| 770 |
}
|
| 771 |
|
| 772 |
+
// inserção a cada λ passos — v0.9.0: portão opcional de ENTROPIA
|
| 773 |
+
// (T-G1): só insere quando o erro está concentrado
|
| 774 |
+
// (H(e)/ln V ≤ teto); ErroMaximo (default) preserva a v0.8.0 exata.
|
| 775 |
if passo % cfg.lambda == cfg.lambda - 1 && vertices.len() < cfg.max_vertices {
|
| 776 |
+
let h = entropia_normalizada(&erro);
|
| 777 |
+
let qe_j = if conta_janela > 0 {
|
| 778 |
+
Some(soma_d2_janela / conta_janela as f64)
|
| 779 |
+
} else {
|
| 780 |
+
None
|
| 781 |
+
};
|
| 782 |
+
if passo % cfg.lambda == cfg.lambda - 1 {
|
| 783 |
+
// fecha a janela: 1ª janela é a referência (QE "inicial")
|
| 784 |
+
if qe_janela.is_none() {
|
| 785 |
+
qe_janela = qe_j;
|
| 786 |
+
}
|
| 787 |
+
qe_atual = qe_j;
|
| 788 |
+
soma_d2_janela = 0.0;
|
| 789 |
+
conta_janela = 0;
|
| 790 |
+
}
|
| 791 |
+
let liberado = match cfg.criterio {
|
| 792 |
+
CriterioInsercao::ErroMaximo => true,
|
| 793 |
+
CriterioInsercao::EntropiaConcentrada { teto } => h <= teto,
|
| 794 |
+
CriterioInsercao::EntropiaComPiso { teto, frac_qe } => {
|
| 795 |
+
let nivel_alto = match (qe_atual, qe_janela) {
|
| 796 |
+
(Some(qa), Some(q0)) if q0 > 0.0 => qa > frac_qe * q0,
|
| 797 |
+
_ => true, // sem referência ainda: não bloqueia
|
| 798 |
+
};
|
| 799 |
+
h <= teto || nivel_alto
|
| 800 |
+
}
|
| 801 |
+
};
|
| 802 |
+
if !liberado {
|
| 803 |
+
insercoes_bloqueadas += 1;
|
| 804 |
+
}
|
| 805 |
+
if liberado {
|
| 806 |
// vértice de maior erro q com vizinho de maior erro
|
| 807 |
let q = erro
|
| 808 |
.iter()
|
|
|
|
| 833 |
arestas.push(((f.min(novo), f.max(novo)), 0));
|
| 834 |
insercoes += 1;
|
| 835 |
}
|
| 836 |
+
} // fim do portão de inserção (liberado)
|
| 837 |
}
|
| 838 |
// vértices isolados (sem arestas) permanecem — GNG clássico não remove
|
| 839 |
}
|
|
|
|
| 850 |
erro,
|
| 851 |
eq_medio: eq / n as f64,
|
| 852 |
insercoes,
|
| 853 |
+
insercoes_bloqueadas,
|
| 854 |
})
|
| 855 |
}
|
| 856 |
|
src/telemetria.rs
CHANGED
|
@@ -390,6 +390,25 @@ impl TelemetryHub {
|
|
| 390 |
/// de pesos (crédito/regulação por nó), quebra-cabeça (aceitação/
|
| 391 |
/// confiança) e NLP/NLG (gates/coerência). Métricas ausentes são
|
| 392 |
/// simplesmente omitidas (painel ADITIVO — sem acoplamento rígido).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
pub fn painel_v070(&self, arvore: Option<&crate::arvore_pesos::ArvorePesos>) -> serde_json::Value {
|
| 394 |
let mut p = self.painel_v060();
|
| 395 |
// promove a versão sem perder o conteúdo
|
|
|
|
| 390 |
/// de pesos (crédito/regulação por nó), quebra-cabeça (aceitação/
|
| 391 |
/// confiança) e NLP/NLG (gates/coerência). Métricas ausentes são
|
| 392 |
/// simplesmente omitidas (painel ADITIVO — sem acoplamento rígido).
|
| 393 |
+
|
| 394 |
+
/// Painel v0.9.0 — despacho SIMD + contexto das medições da r-34.
|
| 395 |
+
pub fn painel_v090(&self) -> serde_json::Value {
|
| 396 |
+
let mut p = self.painel_v070(None);
|
| 397 |
+
if let Some(obj) = p.as_object_mut() {
|
| 398 |
+
obj.insert("simd".into(), crate::simd::relatorio());
|
| 399 |
+
obj.insert(
|
| 400 |
+
"v090_nota".into(),
|
| 401 |
+
serde_json::json!(
|
| 402 |
+
"kernels AVX-512 com despacho em runtime (bit-a-bit); medições em r-34 §4/§6: \
|
| 403 |
+
AdamW 1,0-1,2× (limitado por memória), BMU argmin: escalar ótimo em d=4; \
|
| 404 |
+
bf16 em CPU candle: 3× MAIS LENTO (upcast por op); nosso AdamW 1,5-1,7× mais \
|
| 405 |
+
rápido que candle_nn::optim"
|
| 406 |
+
),
|
| 407 |
+
);
|
| 408 |
+
}
|
| 409 |
+
p
|
| 410 |
+
}
|
| 411 |
+
|
| 412 |
pub fn painel_v070(&self, arvore: Option<&crate::arvore_pesos::ArvorePesos>) -> serde_json::Value {
|
| 413 |
let mut p = self.painel_v060();
|
| 414 |
// promove a versão sem perder o conteúdo
|
src/tokenizador.rs
CHANGED
|
@@ -392,44 +392,9 @@ impl TokenizadorConceitos {
|
|
| 392 |
}
|
| 393 |
let txt = serde_json::to_string(&serde_json::Value::Object(obj))
|
| 394 |
.map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?;
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
/// Carrega com verificação de integridade (manifesto SHA-256 — v0.10.0).
|
| 399 |
-
pub fn carregar_verificado(&mut self, caminho: &str) -> Result<()> {
|
| 400 |
-
let txt = crate::checkpoint::carregar_verificado(caminho)?;
|
| 401 |
-
self.carregar_de_txt(&txt)
|
| 402 |
-
}
|
| 403 |
-
|
| 404 |
-
/// Carregamento nativo (v0.10.0, aditivo): mesmo formato de `salvar`
|
| 405 |
-
/// (`v{i}` → {forma, dados}); o helper Python equivalente permanece.
|
| 406 |
-
pub fn carregar(&mut self, caminho: &str) -> Result<()> {
|
| 407 |
-
let txt = std::fs::read_to_string(caminho)
|
| 408 |
-
.map_err(|e| candle_core::Error::Msg(format!("io: {e}")))?;
|
| 409 |
-
self.carregar_de_txt(&txt)
|
| 410 |
-
}
|
| 411 |
-
|
| 412 |
-
fn carregar_de_txt(&mut self, txt: &str) -> Result<()> {
|
| 413 |
-
let v: serde_json::Value = serde_json::from_str(txt)
|
| 414 |
-
.map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?;
|
| 415 |
-
let obj = v.as_object()
|
| 416 |
-
.ok_or_else(|| candle_core::Error::Msg("formato de checkpoint inválido".into()))?;
|
| 417 |
-
let vars = self.vars();
|
| 418 |
-
for (i, var) in vars.iter().enumerate() {
|
| 419 |
-
let e = obj.get(&format!("v{i}")).ok_or_else(|| {
|
| 420 |
-
candle_core::Error::Msg(format!("checkpoint sem v{i}"))
|
| 421 |
-
})?;
|
| 422 |
-
let forma: Vec<usize> = serde_json::from_value(e["forma"].clone())
|
| 423 |
-
.map_err(|e| candle_core::Error::Msg(format!("forma: {e}")))?;
|
| 424 |
-
let dados: Vec<f32> = serde_json::from_value(e["dados"].clone())
|
| 425 |
-
.map_err(|e| candle_core::Error::Msg(format!("dados: {e}")))?;
|
| 426 |
-
if dados.len() != forma.iter().product::<usize>() {
|
| 427 |
-
candle_core::bail!(
|
| 428 |
-
"forma {:?} incompatível com {} valores", forma, dados.len());
|
| 429 |
-
}
|
| 430 |
-
let t = Tensor::from_vec(dados, forma, var.as_tensor().device())?;
|
| 431 |
-
var.set(&t)?;
|
| 432 |
-
}
|
| 433 |
Ok(())
|
| 434 |
}
|
| 435 |
}
|
|
|
|
| 392 |
}
|
| 393 |
let txt = serde_json::to_string(&serde_json::Value::Object(obj))
|
| 394 |
.map_err(|e| candle_core::Error::Msg(format!("serde: {e}")))?;
|
| 395 |
+
let tmp = format!("{caminho}.tmp");
|
| 396 |
+
std::fs::write(&tmp, txt).map_err(|e| candle_core::Error::Msg(format!("io: {e}")))?;
|
| 397 |
+
std::fs::rename(&tmp, caminho).map_err(|e| candle_core::Error::Msg(format!("rename: {e}")))?;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
Ok(())
|
| 399 |
}
|
| 400 |
}
|
src/treino.rs
CHANGED
|
@@ -39,6 +39,12 @@ impl AdamWManual {
|
|
| 39 |
}
|
| 40 |
|
| 41 |
/// Um passo. `grads`: por variável, gradiente ACHATADO (None = zero).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
pub fn passo(&mut self, grads: Vec<Option<Vec<f32>>>) {
|
| 43 |
self.t += 1;
|
| 44 |
let bc1 = 1.0 - self.beta1.powi(self.t as i32);
|
|
@@ -50,14 +56,10 @@ impl AdamWManual {
|
|
| 50 |
let v = self.v.entry(i).or_insert(vec![0f32; n]);
|
| 51 |
let dados = var.as_tensor().flatten_all().unwrap().to_vec1::<f32>().unwrap();
|
| 52 |
let mut novos = vec![0f32; n];
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
let vh = v[j] / bc2 as f32;
|
| 58 |
-
// AdamW: decaimento desacoplado
|
| 59 |
-
novos[j] = dados[j] - (self.lr as f32) * (mh / (vh.sqrt() + self.eps as f32) + self.wd as f32 * dados[j]);
|
| 60 |
-
}
|
| 61 |
let forma = var.as_tensor().shape();
|
| 62 |
let dev = var.as_tensor().device();
|
| 63 |
let novo_t = Tensor::from_vec(novos, forma.clone(), dev).unwrap()
|
|
@@ -93,9 +95,8 @@ impl AdamWManual {
|
|
| 93 |
if norma > teto && norma > 0.0 {
|
| 94 |
let f = (teto / norma) as f32;
|
| 95 |
for g in grads.iter_mut().flatten() {
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
}
|
| 99 |
}
|
| 100 |
}
|
| 101 |
}
|
|
@@ -199,17 +200,16 @@ impl DadosTreino {
|
|
| 199 |
}
|
| 200 |
}
|
| 201 |
|
| 202 |
-
/// Callback
|
| 203 |
-
|
| 204 |
-
/// o que forçava ESTE módulo de domínio a depender da FFI. A ponte Python
|
| 205 |
-
/// (src/python.rs) embrulha um callable em `Arc<dyn Fn(&serde_json::Value)>`
|
| 206 |
-
/// — `treino.rs` compila e testa sem o interpretador Python.
|
| 207 |
-
pub type CallbackTreino = Option<std::sync::Arc<dyn Fn(&serde_json::Value) + Send + Sync>>;
|
| 208 |
|
| 209 |
-
|
| 210 |
-
pub fn chamar_callback(cb: &CallbackTreino, dados: &serde_json::Value) {
|
| 211 |
if let Some(f) = cb {
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
}
|
| 214 |
}
|
| 215 |
|
|
@@ -229,7 +229,7 @@ pub fn treinar_fase_a(
|
|
| 229 |
cfg: &ConfigKhtst,
|
| 230 |
dados: &DadosTreino,
|
| 231 |
hub: &mut TelemetryHub,
|
| 232 |
-
callback: &
|
| 233 |
caminho_checkpoint: Option<&str>,
|
| 234 |
teto_seg: Option<f64>,
|
| 235 |
) -> Result<ResumoFase> {
|
|
@@ -344,7 +344,7 @@ pub fn treinar_fase_b(
|
|
| 344 |
tok: &TokenizadorConceitos,
|
| 345 |
dados: &DadosTreino,
|
| 346 |
hub: &mut TelemetryHub,
|
| 347 |
-
callback: &
|
| 348 |
caminho_checkpoint: Option<&str>,
|
| 349 |
teto_seg: Option<f64>,
|
| 350 |
) -> Result<ResumoFase> {
|
|
@@ -513,7 +513,7 @@ pub fn treinar_fase_c(
|
|
| 513 |
tok: &TokenizadorConceitos,
|
| 514 |
dados: &DadosTreino,
|
| 515 |
hub: &mut TelemetryHub,
|
| 516 |
-
callback: &
|
| 517 |
teto_seg: Option<f64>,
|
| 518 |
passos: usize,
|
| 519 |
caminho_gerador: Option<&str>,
|
|
@@ -1141,3 +1141,82 @@ fn decaimento_geometrico(v: &[f64]) -> f64 {
|
|
| 1141 |
1.0
|
| 1142 |
}
|
| 1143 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
}
|
| 40 |
|
| 41 |
/// Um passo. `grads`: por variável, gradiente ACHATADO (None = zero).
|
| 42 |
+
///
|
| 43 |
+
/// v0.9.0: o loop interno é o kernel fundido `simd::passo_adamw` (AVX-512
|
| 44 |
+
/// com despacho em runtime + fallback escalar) — réplica EXATA da ordem
|
| 45 |
+
/// de operações do loop escalar da v0.8.0 (incluindo `(1.0 - beta as f32)`
|
| 46 |
+
/// pós-cast), sem FMA/rsqrt ⇒ saída bit-a-bit idêntica (Teo X-1, r-34 §2;
|
| 47 |
+
/// teste `adamw_bit_a_bit`).
|
| 48 |
pub fn passo(&mut self, grads: Vec<Option<Vec<f32>>>) {
|
| 49 |
self.t += 1;
|
| 50 |
let bc1 = 1.0 - self.beta1.powi(self.t as i32);
|
|
|
|
| 56 |
let v = self.v.entry(i).or_insert(vec![0f32; n]);
|
| 57 |
let dados = var.as_tensor().flatten_all().unwrap().to_vec1::<f32>().unwrap();
|
| 58 |
let mut novos = vec![0f32; n];
|
| 59 |
+
crate::simd::passo_adamw(
|
| 60 |
+
&dados, &g, m, v, &mut novos, self.lr, self.beta1, self.beta2,
|
| 61 |
+
self.eps, self.wd, bc1, bc2,
|
| 62 |
+
);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
let forma = var.as_tensor().shape();
|
| 64 |
let dev = var.as_tensor().device();
|
| 65 |
let novo_t = Tensor::from_vec(novos, forma.clone(), dev).unwrap()
|
|
|
|
| 95 |
if norma > teto && norma > 0.0 {
|
| 96 |
let f = (teto / norma) as f32;
|
| 97 |
for g in grads.iter_mut().flatten() {
|
| 98 |
+
// v0.9.0: kernel SIMD bit-a-bit (uma mul por elemento)
|
| 99 |
+
crate::simd::escalar_slice(g, f);
|
|
|
|
| 100 |
}
|
| 101 |
}
|
| 102 |
}
|
|
|
|
| 200 |
}
|
| 201 |
}
|
| 202 |
|
| 203 |
+
/// Callback Python opcional (chamado a cada `a_cada` passos com um dict).
|
| 204 |
+
pub type CallbackPy = Option<pyo3::Py<pyo3::PyAny>>;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
|
| 206 |
+
pub fn chamar_callback(cb: &CallbackPy, dados: &serde_json::Value) {
|
|
|
|
| 207 |
if let Some(f) = cb {
|
| 208 |
+
pyo3::Python::with_gil(|py| {
|
| 209 |
+
if let Ok(obj) = pythonize::pythonize(py, dados) {
|
| 210 |
+
let _ = f.call1(py, (obj,));
|
| 211 |
+
}
|
| 212 |
+
});
|
| 213 |
}
|
| 214 |
}
|
| 215 |
|
|
|
|
| 229 |
cfg: &ConfigKhtst,
|
| 230 |
dados: &DadosTreino,
|
| 231 |
hub: &mut TelemetryHub,
|
| 232 |
+
callback: &CallbackPy,
|
| 233 |
caminho_checkpoint: Option<&str>,
|
| 234 |
teto_seg: Option<f64>,
|
| 235 |
) -> Result<ResumoFase> {
|
|
|
|
| 344 |
tok: &TokenizadorConceitos,
|
| 345 |
dados: &DadosTreino,
|
| 346 |
hub: &mut TelemetryHub,
|
| 347 |
+
callback: &CallbackPy,
|
| 348 |
caminho_checkpoint: Option<&str>,
|
| 349 |
teto_seg: Option<f64>,
|
| 350 |
) -> Result<ResumoFase> {
|
|
|
|
| 513 |
tok: &TokenizadorConceitos,
|
| 514 |
dados: &DadosTreino,
|
| 515 |
hub: &mut TelemetryHub,
|
| 516 |
+
callback: &CallbackPy,
|
| 517 |
teto_seg: Option<f64>,
|
| 518 |
passos: usize,
|
| 519 |
caminho_gerador: Option<&str>,
|
|
|
|
| 1141 |
1.0
|
| 1142 |
}
|
| 1143 |
}
|
| 1144 |
+
|
| 1145 |
+
#[cfg(test)]
|
| 1146 |
+
mod testes_v090 {
|
| 1147 |
+
use super::*;
|
| 1148 |
+
use candle_nn::optim::{AdamW as AdamWCandle, Optimizer, ParamsAdamW};
|
| 1149 |
+
|
| 1150 |
+
/// **X-3 (oráculo independente — crítica Qwen3.8 nº 2, r-34 §2):** treina
|
| 1151 |
+
/// o MESMO problema convexo (regressão linear por MSE) com o nosso
|
| 1152 |
+
/// `AdamWManual` e com o `candle_nn::optim::AdamW` do próprio framework.
|
| 1153 |
+
/// As fórmulas são idênticas (AdamW desacoplado), mas a ORDEM de ops difere
|
| 1154 |
+
/// (divisão por bc vs multiplicação pelo recíproco; ponto flutuante não é
|
| 1155 |
+
/// associativo) ⇒ a igualdade é em ε, não bit-a-bit. Se as trajetórias
|
| 1156 |
+
/// divergissem além de ε, o nosso otimizador teria um bug de fórmula.
|
| 1157 |
+
#[test]
|
| 1158 |
+
fn adamw_oraculo_candle_nn() {
|
| 1159 |
+
let dev = Device::Cpu;
|
| 1160 |
+
// dados determinísticos: x (128, 8), alvo = x @ W* (sem ruído)
|
| 1161 |
+
let n = 128usize;
|
| 1162 |
+
let d = 8usize;
|
| 1163 |
+
let k = 4usize;
|
| 1164 |
+
let xs: Vec<f32> = (0..n * d).map(|i| ((i as f32) * 0.37) % 1.0 - 0.5).collect();
|
| 1165 |
+
let x = Tensor::from_vec(xs, (n, d), &dev).unwrap();
|
| 1166 |
+
let w_star: Vec<f32> = (0..d * k).map(|i| ((i as f32) * 0.11).sin() * 0.5).collect();
|
| 1167 |
+
let w_star_t = Tensor::from_vec(w_star.clone(), (d, k), &dev).unwrap();
|
| 1168 |
+
let y = x.matmul(&w_star_t).unwrap();
|
| 1169 |
+
|
| 1170 |
+
// mesma inicialização para os dois otimizadores (determinística)
|
| 1171 |
+
let w_inicial: Vec<f32> = (0..d * k).map(|i| ((i as f32) * 0.7) % 0.2 - 0.1).collect();
|
| 1172 |
+
let (lr, wd) = (0.05f64, 0.0);
|
| 1173 |
+
|
| 1174 |
+
let roda = |usar_nosso: bool| -> Vec<f32> {
|
| 1175 |
+
let w_t = Tensor::from_vec(w_inicial.clone(), (d, k), &dev).unwrap();
|
| 1176 |
+
let w_var = Var::from_tensor(&w_t).unwrap();
|
| 1177 |
+
let vars = vec![w_var.clone()];
|
| 1178 |
+
let mut nosso = AdamWManual::novo(vars.clone(), lr, wd);
|
| 1179 |
+
let params = ParamsAdamW { lr, beta1: 0.9, beta2: 0.999, eps: 1e-8, weight_decay: wd };
|
| 1180 |
+
let mut candle_opt = AdamWCandle::new(vars.clone(), params).unwrap();
|
| 1181 |
+
for passo in 0..60 {
|
| 1182 |
+
let predita = x.matmul(w_var.as_tensor()).unwrap();
|
| 1183 |
+
let erro = predita.sub(&y).unwrap();
|
| 1184 |
+
let perda = erro.sqr().unwrap().mean_all().unwrap();
|
| 1185 |
+
if usar_nosso {
|
| 1186 |
+
let grads = grads_achatados(&perda, &vars).unwrap();
|
| 1187 |
+
// sem clip: comparação de FÓRMULA, não do mecanismo de segurança
|
| 1188 |
+
nosso.passo(grads);
|
| 1189 |
+
} else {
|
| 1190 |
+
let gs = perda.backward().unwrap();
|
| 1191 |
+
candle_opt.step(&gs).unwrap();
|
| 1192 |
+
}
|
| 1193 |
+
let _ = passo;
|
| 1194 |
+
}
|
| 1195 |
+
w_var
|
| 1196 |
+
.as_tensor()
|
| 1197 |
+
.flatten_all()
|
| 1198 |
+
.unwrap()
|
| 1199 |
+
.to_vec1::<f32>()
|
| 1200 |
+
.unwrap()
|
| 1201 |
+
};
|
| 1202 |
+
|
| 1203 |
+
let fim_nosso = roda(true);
|
| 1204 |
+
let fim_candle = roda(false);
|
| 1205 |
+
let mut max_dif = 0f32;
|
| 1206 |
+
for (a, b) in fim_nosso.iter().zip(fim_candle.iter()) {
|
| 1207 |
+
max_dif = max_dif.max((a - b).abs());
|
| 1208 |
+
}
|
| 1209 |
+
let escala = fim_nosso.iter().map(|v| v.abs()).fold(0f32, f32::max).max(1e-6);
|
| 1210 |
+
assert!(
|
| 1211 |
+
max_dif / escala < 1e-4,
|
| 1212 |
+
"AdamWManual divergiu do oráculo candle_nn: max|Δ|={max_dif} (escala {escala})"
|
| 1213 |
+
);
|
| 1214 |
+
// e ambos convergiram perto do mínimo (o alvo é exato)
|
| 1215 |
+
let erro_final = fim_nosso
|
| 1216 |
+
.iter()
|
| 1217 |
+
.zip(w_star.iter())
|
| 1218 |
+
.map(|(a, b)| (a - b).abs())
|
| 1219 |
+
.fold(0f32, f32::max);
|
| 1220 |
+
assert!(erro_final < 0.05, "regressão não convergiu: {erro_final}");
|
| 1221 |
+
}
|
| 1222 |
+
}
|
src/verificador.rs
CHANGED
|
@@ -60,19 +60,6 @@ fn nll_temperatura(logits: &Tensor, alvos: &Tensor, t: f64) -> Result<f64> {
|
|
| 60 |
Ok(ce.to_scalar::<f32>()? as f64)
|
| 61 |
}
|
| 62 |
|
| 63 |
-
/// **T-V3 (v0.10.0):** fronteira TIPADA — `TemperaturaInversa` carrega a
|
| 64 |
-
/// pré-condição γ ∈ (0, +∞) da convexidade N-2 por construção; esta
|
| 65 |
-
/// variante recusa temperaturas inválidas NA ASSINATURA, sem custo em
|
| 66 |
-
/// runtime além da validação única já feita no construtor. Equivalente
|
| 67 |
-
/// bit-a-bit a `nll_temperatura(t.temperatura())` (teste tv3d).
|
| 68 |
-
pub fn nll_temperatura_tipado(
|
| 69 |
-
logits: &Tensor,
|
| 70 |
-
alvos: &Tensor,
|
| 71 |
-
t: &crate::tipos::TemperaturaInversa,
|
| 72 |
-
) -> Result<f64> {
|
| 73 |
-
nll_temperatura(logits, alvos, t.temperatura())
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
/// **V-1:** calibra T* por golden-section em `log T ∈ [ln(1/8), ln 8]`
|
| 77 |
/// minimizando a NLL nos dados retidos. Devolve (T*, nll(1), nll(T*)).
|
| 78 |
pub fn calibrar_temperatura(logits: &Tensor, alvos: &Tensor) -> Result<(f64, f64, f64)> {
|
|
@@ -289,10 +276,6 @@ impl VerificadorNcm {
|
|
| 289 |
let (tg, n1g, ntg) = calibrar_temperatura(logits, alvos)?;
|
| 290 |
(t, nll1, nllt) = (tg, n1g, ntg);
|
| 291 |
}
|
| 292 |
-
// T-V3: saída do otimizador VALIDADA antes de armazenar — se
|
| 293 |
-
// Newton e golden produzirem lixo (não deveriam), falha alto em
|
| 294 |
-
// vez de propagar γ ≤ 0 silenciosamente (1/0 = NaN/∞ adiante).
|
| 295 |
-
let _ti = crate::tipos::TemperaturaInversa::nova(t)?;
|
| 296 |
self.temperatura = t;
|
| 297 |
self.n_ll_antes = nll1;
|
| 298 |
self.n_ll_depois = nllt;
|
|
@@ -314,19 +297,6 @@ impl VerificadorNcm {
|
|
| 314 |
Ok((e_antes, e_depois))
|
| 315 |
}
|
| 316 |
|
| 317 |
-
/// Define a temperatura pela fronteira TIPADA (T-V3) — recusa
|
| 318 |
-
/// T ∉ (0, +∞) na fronteira; o campo `pub` permanece para leitura
|
| 319 |
-
/// e compatibilidade v0.9.0 (nada é removido).
|
| 320 |
-
pub fn definir_temperatura_tipado(&mut self, t: &crate::tipos::TemperaturaInversa) {
|
| 321 |
-
self.temperatura = t.temperatura();
|
| 322 |
-
}
|
| 323 |
-
|
| 324 |
-
/// Leitura TIPADA: valida o campo `pub` na saída (cinto-e-suspensório
|
| 325 |
-
/// para escritas diretas legadas) — Err se alguém atribuiu T inválida.
|
| 326 |
-
pub fn temperatura_tipada(&self) -> Result<crate::tipos::TemperaturaInversa> {
|
| 327 |
-
crate::tipos::TemperaturaInversa::nova(self.temperatura)
|
| 328 |
-
}
|
| 329 |
-
|
| 330 |
/// Confiança calibrada (n, K) → (n,) — a decisão do ciclo d usa isto.
|
| 331 |
pub fn confianca(&self, logits: &Tensor) -> Result<Tensor> {
|
| 332 |
let escalado = logits.affine(1.0 / self.temperatura, 0.0)?;
|
|
@@ -578,46 +548,3 @@ mod testes {
|
|
| 578 |
assert!((1.0 / 8.0..=8.0).contains(&t_n2));
|
| 579 |
}
|
| 580 |
}
|
| 581 |
-
|
| 582 |
-
mod testes_temperatura_tipada {
|
| 583 |
-
use super::*;
|
| 584 |
-
use crate::tipos::TemperaturaInversa;
|
| 585 |
-
|
| 586 |
-
/// tv3d — equivalência bit-a-bit da fronteira tipada.
|
| 587 |
-
#[test]
|
| 588 |
-
fn tv3d_nll_tipada_equivalente() {
|
| 589 |
-
let dev = candle_core::Device::Cpu;
|
| 590 |
-
let logits = Tensor::randn(0f32, 1f32, (64, 8), &dev).unwrap();
|
| 591 |
-
// alvos = argmax de ruído independente (u32 válido por construção)
|
| 592 |
-
let alvos = Tensor::randn(0f32, 1f32, (64, 8), &dev).unwrap()
|
| 593 |
-
.argmax(candle_core::D::Minus1).unwrap()
|
| 594 |
-
.to_dtype(candle_core::DType::U32).unwrap();
|
| 595 |
-
for t_bom in [0.5, 1.0, 2.0, 3.7] {
|
| 596 |
-
let ti = TemperaturaInversa::nova(t_bom).unwrap();
|
| 597 |
-
let a = nll_temperatura(&logits, &alvos, t_bom).unwrap();
|
| 598 |
-
let b = nll_temperatura_tipado(&logits, &alvos, &ti).unwrap();
|
| 599 |
-
assert_eq!(a.to_bits(), b.to_bits(), "bit-a-bit em T={t_bom}");
|
| 600 |
-
}
|
| 601 |
-
}
|
| 602 |
-
|
| 603 |
-
/// tv3e — ajustar devolve temperatura válida na fronteira tipada;
|
| 604 |
-
/// setter tipado recusa inválidas; getter detecta escrita legada má.
|
| 605 |
-
#[test]
|
| 606 |
-
fn tv3e_verificador_fronteira() {
|
| 607 |
-
let dev = candle_core::Device::Cpu;
|
| 608 |
-
let logits = Tensor::randn(0f32, 2f32, (128, 16), &dev).unwrap();
|
| 609 |
-
let alvos = Tensor::randn(0f32, 1f32, (128, 16), &dev).unwrap()
|
| 610 |
-
.argmax(candle_core::D::Minus1).unwrap()
|
| 611 |
-
.to_dtype(candle_core::DType::U32).unwrap();
|
| 612 |
-
let mut v = VerificadorNcm::novo();
|
| 613 |
-
v.ajustar(&logits, &alvos).unwrap();
|
| 614 |
-
assert!(v.temperatura_tipada().is_ok(), "T* do ajustar é válida");
|
| 615 |
-
|
| 616 |
-
let ti = TemperaturaInversa::nova(1.25).unwrap();
|
| 617 |
-
v.definir_temperatura_tipado(&ti);
|
| 618 |
-
assert_eq!(v.temperatura, 1.25);
|
| 619 |
-
|
| 620 |
-
v.temperatura = -3.0; // escrita legada má (campo pub)
|
| 621 |
-
assert!(v.temperatura_tipada().is_err(), "getter detecta γ inválido");
|
| 622 |
-
}
|
| 623 |
-
}
|
|
|
|
| 60 |
Ok(ce.to_scalar::<f32>()? as f64)
|
| 61 |
}
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
/// **V-1:** calibra T* por golden-section em `log T ∈ [ln(1/8), ln 8]`
|
| 64 |
/// minimizando a NLL nos dados retidos. Devolve (T*, nll(1), nll(T*)).
|
| 65 |
pub fn calibrar_temperatura(logits: &Tensor, alvos: &Tensor) -> Result<(f64, f64, f64)> {
|
|
|
|
| 276 |
let (tg, n1g, ntg) = calibrar_temperatura(logits, alvos)?;
|
| 277 |
(t, nll1, nllt) = (tg, n1g, ntg);
|
| 278 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
self.temperatura = t;
|
| 280 |
self.n_ll_antes = nll1;
|
| 281 |
self.n_ll_depois = nllt;
|
|
|
|
| 297 |
Ok((e_antes, e_depois))
|
| 298 |
}
|
| 299 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
/// Confiança calibrada (n, K) → (n,) — a decisão do ciclo d usa isto.
|
| 301 |
pub fn confianca(&self, logits: &Tensor) -> Result<Tensor> {
|
| 302 |
let escalado = logits.affine(1.0 / self.temperatura, 0.0)?;
|
|
|
|
| 548 |
assert!((1.0 / 8.0..=8.0).contains(&t_n2));
|
| 549 |
}
|
| 550 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|