torch-type-nn 0.1.0.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- torch_type_nn-0.1.0.dev0/.gitignore +15 -0
- torch_type_nn-0.1.0.dev0/BOARD.md +256 -0
- torch_type_nn-0.1.0.dev0/LICENSE +23 -0
- torch_type_nn-0.1.0.dev0/PKG-INFO +302 -0
- torch_type_nn-0.1.0.dev0/README.md +274 -0
- torch_type_nn-0.1.0.dev0/benchmarks/bench.py +421 -0
- torch_type_nn-0.1.0.dev0/benchmarks/board.py +230 -0
- torch_type_nn-0.1.0.dev0/benchmarks/data/README.md +31 -0
- torch_type_nn-0.1.0.dev0/benchmarks/datasets.json +26 -0
- torch_type_nn-0.1.0.dev0/benchmarks/fetch_data.py +68 -0
- torch_type_nn-0.1.0.dev0/benchmarks/reference/c-type-nn-7333bf7-30seeds.jsonl +18 -0
- torch_type_nn-0.1.0.dev0/benchmarks/results/board-per-sample.jsonl +36 -0
- torch_type_nn-0.1.0.dev0/benchmarks/results/scale-batch32.jsonl +10 -0
- torch_type_nn-0.1.0.dev0/benchmarks/scale.py +237 -0
- torch_type_nn-0.1.0.dev0/docs/ADAPTERS.md +94 -0
- torch_type_nn-0.1.0.dev0/docs/AUDIT.md +63 -0
- torch_type_nn-0.1.0.dev0/docs/DIFFERENCES.md +131 -0
- torch_type_nn-0.1.0.dev0/flake.lock +78 -0
- torch_type_nn-0.1.0.dev0/flake.nix +308 -0
- torch_type_nn-0.1.0.dev0/pyproject.toml +53 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/__init__.py +38 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/adapters/__init__.py +6 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/adapters/mlp.py +322 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/adapters/typenn.py +546 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/edits.py +99 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/functional.py +126 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/layer.py +354 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/mlp.py +251 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/network.py +115 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/optim.py +122 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/protocol.py +148 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/py.typed +0 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/scaling.py +523 -0
- torch_type_nn-0.1.0.dev0/src/torch_type_nn/train.py +97 -0
- torch_type_nn-0.1.0.dev0/tests/c/crosscheck.c +91 -0
- torch_type_nn-0.1.0.dev0/tests/conftest.py +33 -0
- torch_type_nn-0.1.0.dev0/tests/data/iris_prune_boundary.pt +0 -0
- torch_type_nn-0.1.0.dev0/tests/helpers.py +62 -0
- torch_type_nn-0.1.0.dev0/tests/test_board.py +59 -0
- torch_type_nn-0.1.0.dev0/tests/test_crosscheck_c.py +100 -0
- torch_type_nn-0.1.0.dev0/tests/test_datasets.py +106 -0
- torch_type_nn-0.1.0.dev0/tests/test_device.py +208 -0
- torch_type_nn-0.1.0.dev0/tests/test_edits.py +150 -0
- torch_type_nn-0.1.0.dev0/tests/test_functional.py +151 -0
- torch_type_nn-0.1.0.dev0/tests/test_layer.py +104 -0
- torch_type_nn-0.1.0.dev0/tests/test_mlp.py +177 -0
- torch_type_nn-0.1.0.dev0/tests/test_optim.py +104 -0
- torch_type_nn-0.1.0.dev0/tests/test_package.py +11 -0
- torch_type_nn-0.1.0.dev0/tests/test_scaling.py +333 -0
- torch_type_nn-0.1.0.dev0/tests/test_threshold.py +270 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.ruff_cache/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
result
|
|
9
|
+
result-*
|
|
10
|
+
.direnv/
|
|
11
|
+
benchmarks/out/
|
|
12
|
+
# benchmark data: pinned in benchmarks/datasets.json, fetched by `nix develop`
|
|
13
|
+
# (or benchmarks/fetch_data.py), never committed
|
|
14
|
+
benchmarks/data/*
|
|
15
|
+
!benchmarks/data/README.md
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Board
|
|
2
|
+
|
|
3
|
+
Every number on this page is generated by `benchmarks/board.py` from the
|
|
4
|
+
result files it names; the prose only says what each comparison tests.
|
|
5
|
+
Regenerate everything (all cores, CPU) with:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
nix run .#board-all # bench.py + scale.py with --jobs $(nproc), then board.py --write BOARD.md
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
or step by step:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
nix run .#bench -- all all --seeds 5 --jobs 0 > benchmarks/results/board-per-sample.jsonl
|
|
15
|
+
nix run .#scale -- all --jobs 0 > benchmarks/results/scale-batch32.jsonl
|
|
16
|
+
nix run .#board -- --write BOARD.md
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
A hold-out gap is **clear** only when it exceeds two standard errors of the
|
|
20
|
+
difference, `2 sqrt(sd_a²/n_a + sd_b²/n_b)` (the C board's rule); otherwise it
|
|
21
|
+
is *within noise*. "params ratio" is the first model's mean parameter count
|
|
22
|
+
over the second's.
|
|
23
|
+
|
|
24
|
+
## The models
|
|
25
|
+
|
|
26
|
+
| model | what it is |
|
|
27
|
+
|---|---|
|
|
28
|
+
| **torch type-nn** | the architecture: threshold rule (every decision from back-prop), width on every junction; `TypeNN` + `TypeAdam` |
|
|
29
|
+
| torch type-nn-bic | the same with the BIC prior (`rule="bic"`, measured on training pairs) |
|
|
30
|
+
| torch ref-type-nn | C's `type-nn` exactly: `rule="bic"`, `width_through_depth_probe=False` |
|
|
31
|
+
| torch ref-type-nn-overfit | C's `type-nn-overfit` exactly: threshold rule, `width_through_depth_probe=False` |
|
|
32
|
+
| torch MLP | the C board's baseline: Linear-ReLU-Linear, 8 hidden units (n ≤ 4) or 16, stock Adam |
|
|
33
|
+
| torch MLP, scaled | `ScalableMLP` grown and pruned by the default (threshold) rule, stock Adam |
|
|
34
|
+
| C type-nn, C type-nn-overfit, C c-mlp | hadilq/type-nn `7333bf7`, run on the same machine as its committed reference (deterministic; reproduces digit for digit) |
|
|
35
|
+
|
|
36
|
+
## Per-sample board
|
|
37
|
+
|
|
38
|
+
`benchmarks/bench.py`, a line-by-line port of `bench.c`: the same data files
|
|
39
|
+
and parsing, the fixed 70/30 split (xorshift32 Fisher-Yates, seed 34972),
|
|
40
|
+
train-only standardisation, the same per-epoch shuffle, per-sample Adam at
|
|
41
|
+
`lr × 0.1`, epochs and lr per task, 5 initialisation seeds per cell (mean;
|
|
42
|
+
± is the standard deviation over seeds). Only the initialisation RNG differs
|
|
43
|
+
from C (torch's generator), so single runs are not bit-identical to C.
|
|
44
|
+
With `--jobs` every (task, model, seed) runs in its own single-threaded
|
|
45
|
+
process; the results are identical to a serial run, but `train s` is
|
|
46
|
+
measured with all cores busy, so compare it only within one run.
|
|
47
|
+
|
|
48
|
+
<!-- board:per-sample -->
|
|
49
|
+
Generated by `benchmarks/board.py` from `benchmarks/reference/c-type-nn-7333bf7-30seeds.jsonl`, `benchmarks/results/board-per-sample.jsonl` (5 seeds, batch 1, cpu).
|
|
50
|
+
|
|
51
|
+
| task | model | hold MSE | ± sd | hold acc | train MSE | params | ± sd | layers | train s | µs/inf |
|
|
52
|
+
|---|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|
|
|
53
|
+
| xor | C type-nn | n/a | n/a | n/a | 1.67e-02 | 23.6 | 5.8 | 1→1.9 | 0.01 | 0.1 |
|
|
54
|
+
| xor | C type-nn-overfit | n/a | n/a | n/a | 2.03e-04 | 78.8 | 47.4 | 1→2.7 | 0.01 | 0.2 |
|
|
55
|
+
| xor | C c-mlp | n/a | n/a | n/a | 8.76e-03 | 33.0 | 0.0 | 2→2.0 | 0.00 | 0.0 |
|
|
56
|
+
| xor | torch ref-type-nn | n/a | n/a | n/a | 8.40e-06 | 24.8 | 1.8 | 1→2.0 | 67.24 | 319.7 |
|
|
57
|
+
| xor | torch ref-type-nn-overfit | n/a | n/a | n/a | 4.90e-06 | 56.0 | 17.0 | 1→2.4 | 46.32 | 497.0 |
|
|
58
|
+
| xor | **torch type-nn** | n/a | n/a | n/a | 5.11e-06 | 63.0 | 23.7 | 1→2.4 | 46.60 | 1066.2 |
|
|
59
|
+
| xor | torch type-nn-bic | n/a | n/a | n/a | 8.40e-06 | 24.8 | 1.8 | 1→2.0 | 58.89 | 149.1 |
|
|
60
|
+
| xor | torch MLP | n/a | n/a | n/a | 9.61e-29 | 33.0 | 0.0 | 2→2.0 | 12.39 | 66.9 |
|
|
61
|
+
| xor | torch MLP, scaled | n/a | n/a | n/a | 8.34e-02 | 39.0 | 37.1 | 2→3.2 | 23.44 | 97.2 |
|
|
62
|
+
| iris | C type-nn | 0.0248 | 0.0055 | 0.961 | 3.17e-03 | 120.0 | 45.6 | 3→4.0 | 0.38 | 0.6 |
|
|
63
|
+
| iris | C type-nn-overfit | 0.0240 | 0.0053 | 0.961 | 8.43e-04 | 166.2 | 26.7 | 3→4.0 | 0.07 | 0.6 |
|
|
64
|
+
| iris | C c-mlp | 0.0302 | 0.0052 | 0.956 | 1.24e-02 | 67.0 | 0.0 | 2→2.0 | 0.02 | 0.1 |
|
|
65
|
+
| iris | torch ref-type-nn | 0.0331 | 0.0142 | 0.947 | 6.29e-03 | 144.6 | 27.0 | 3→4.0 | 226.46 | 496.2 |
|
|
66
|
+
| iris | torch ref-type-nn-overfit | 0.0272 | 0.0034 | 0.956 | 8.28e-04 | 159.6 | 33.6 | 3→4.0 | 223.01 | 965.7 |
|
|
67
|
+
| iris | **torch type-nn** | 0.0258 | 0.0019 | 0.956 | 6.23e-04 | 180.0 | 15.1 | 3→4.0 | 225.93 | 1105.1 |
|
|
68
|
+
| iris | torch type-nn-bic | 0.0250 | 0.0062 | 0.964 | 1.37e-04 | 134.8 | 41.1 | 3→4.0 | 243.32 | 456.3 |
|
|
69
|
+
| iris | torch MLP | 0.0309 | 0.0043 | 0.956 | 1.48e-02 | 67.0 | 0.0 | 2→2.0 | 41.09 | 65.9 |
|
|
70
|
+
| iris | torch MLP, scaled | 0.0262 | 0.0044 | 0.956 | 6.08e-03 | 71.8 | 16.3 | 2→3.0 | 83.05 | 193.3 |
|
|
71
|
+
| wine | C type-nn | 0.0241 | 0.0070 | 0.956 | 4.21e-04 | 183.2 | 62.2 | 4→5.1 | 0.52 | 0.5 |
|
|
72
|
+
| wine | C type-nn-overfit | 0.0258 | 0.0099 | 0.954 | 2.94e-04 | 378.0 | 47.3 | 4→4.9 | 0.11 | 0.9 |
|
|
73
|
+
| wine | C c-mlp | 0.0243 | 0.0042 | 0.970 | 2.68e-03 | 275.0 | 0.0 | 2→2.0 | 0.03 | 0.2 |
|
|
74
|
+
| wine | torch ref-type-nn | 0.0198 | 0.0070 | 0.959 | 3.20e-05 | 214.6 | 34.6 | 4→5.0 | 249.18 | 589.4 |
|
|
75
|
+
| wine | torch ref-type-nn-overfit | 0.0364 | 0.0107 | 0.933 | 2.72e-05 | 357.0 | 14.6 | 4→5.0 | 239.02 | 1400.0 |
|
|
76
|
+
| wine | **torch type-nn** | 0.0189 | 0.0066 | 0.967 | 6.97e-05 | 486.2 | 15.5 | 4→5.0 | 241.50 | 579.0 |
|
|
77
|
+
| wine | torch type-nn-bic | 0.0264 | 0.0072 | 0.952 | 4.31e-05 | 194.2 | 72.0 | 4→4.8 | 263.42 | 1204.3 |
|
|
78
|
+
| wine | torch MLP | 0.0259 | 0.0032 | 0.956 | 3.87e-03 | 275.0 | 0.0 | 2→2.0 | 37.77 | 71.8 |
|
|
79
|
+
| wine | torch MLP, scaled | 0.0177 | 0.0078 | 0.967 | 3.23e-04 | 124.6 | 32.7 | 2→3.0 | 70.93 | 176.3 |
|
|
80
|
+
| wdbc | C type-nn | 0.0488 | 0.0089 | 0.946 | 2.07e-03 | 153.0 | 36.5 | 3→4.0 | 0.17 | 0.3 |
|
|
81
|
+
| wdbc | C type-nn-overfit | 0.0472 | 0.0077 | 0.948 | 2.85e-03 | 176.6 | 40.5 | 3→3.4 | 0.06 | 0.2 |
|
|
82
|
+
| wdbc | C c-mlp | 0.0459 | 0.0028 | 0.944 | 4.99e-03 | 513.0 | 0.0 | 2→2.0 | 0.09 | 0.3 |
|
|
83
|
+
| wdbc | torch ref-type-nn | 0.0439 | 0.0056 | 0.952 | 4.88e-04 | 175.8 | 36.3 | 3→4.0 | 241.50 | 1055.6 |
|
|
84
|
+
| wdbc | torch ref-type-nn-overfit | 0.0435 | 0.0059 | 0.950 | 5.14e-04 | 184.8 | 55.6 | 3→3.6 | 227.80 | 165.4 |
|
|
85
|
+
| wdbc | **torch type-nn** | 0.0473 | 0.0037 | 0.949 | 4.94e-04 | 220.0 | 40.7 | 3→3.6 | 235.37 | 1032.9 |
|
|
86
|
+
| wdbc | torch type-nn-bic | 0.0436 | 0.0052 | 0.950 | 1.64e-03 | 210.0 | 83.4 | 3→4.0 | 243.94 | 494.9 |
|
|
87
|
+
| wdbc | torch MLP | 0.0471 | 0.0040 | 0.944 | 5.70e-03 | 513.0 | 0.0 | 2→2.0 | 31.36 | 25.5 |
|
|
88
|
+
| wdbc | torch MLP, scaled | 0.0539 | 0.0079 | 0.943 | 1.84e-03 | 132.0 | 23.3 | 2→3.0 | 85.69 | 80.2 |
|
|
89
|
+
| diabetes | C type-nn | 0.0331 | 0.0014 | n/a | 2.72e-02 | 20.1 | 5.5 | 2→2.5 | 0.06 | 0.1 |
|
|
90
|
+
| diabetes | C type-nn-overfit | 0.0374 | 0.0027 | n/a | 2.41e-02 | 33.0 | 9.9 | 2→2.5 | 0.03 | 0.1 |
|
|
91
|
+
| diabetes | C c-mlp | 0.0476 | 0.0038 | n/a | 1.38e-02 | 193.0 | 0.0 | 2→2.0 | 0.04 | 0.2 |
|
|
92
|
+
| diabetes | torch ref-type-nn | 0.0328 | 0.0005 | n/a | 2.73e-02 | 18.6 | 4.9 | 2→2.2 | 263.32 | 547.7 |
|
|
93
|
+
| diabetes | torch ref-type-nn-overfit | 0.0366 | 0.0030 | n/a | 2.43e-02 | 31.8 | 7.8 | 2→2.4 | 264.63 | 247.2 |
|
|
94
|
+
| diabetes | **torch type-nn** | 0.0501 | 0.0145 | n/a | 1.75e-02 | 100.0 | 42.6 | 2→3.0 | 311.33 | 367.8 |
|
|
95
|
+
| diabetes | torch type-nn-bic | 0.0330 | 0.0006 | n/a | 2.78e-02 | 18.0 | 2.1 | 2→2.6 | 288.10 | 503.3 |
|
|
96
|
+
| diabetes | torch MLP | 0.0478 | 0.0038 | n/a | 1.47e-02 | 193.0 | 0.0 | 2→2.0 | 71.59 | 33.9 |
|
|
97
|
+
| diabetes | torch MLP, scaled | 0.0410 | 0.0017 | n/a | 2.05e-02 | 61.6 | 10.3 | 2→3.0 | 137.15 | 142.8 |
|
|
98
|
+
| ionosphere | C type-nn | 0.0932 | 0.0166 | 0.895 | 1.22e-02 | 146.4 | 60.1 | 4→4.8 | 0.23 | 0.5 |
|
|
99
|
+
| ionosphere | C type-nn-overfit | 0.0811 | 0.0183 | 0.911 | 2.35e-03 | 336.9 | 78.6 | 4→4.4 | 0.10 | 1.0 |
|
|
100
|
+
| ionosphere | C c-mlp | 0.1412 | 0.0172 | 0.899 | 1.59e-02 | 577.0 | 0.0 | 2→2.0 | 0.07 | 0.4 |
|
|
101
|
+
| ionosphere | torch ref-type-nn | 0.0919 | 0.0212 | 0.900 | 7.40e-03 | 157.2 | 59.2 | 4→5.0 | 275.86 | 612.0 |
|
|
102
|
+
| ionosphere | torch ref-type-nn-overfit | 0.0899 | 0.0199 | 0.898 | 1.61e-03 | 319.6 | 71.2 | 4→4.6 | 263.69 | 570.3 |
|
|
103
|
+
| ionosphere | **torch type-nn** | 0.0854 | 0.0209 | 0.909 | 7.06e-04 | 389.6 | 66.3 | 4→4.8 | 263.67 | 1315.4 |
|
|
104
|
+
| ionosphere | torch type-nn-bic | 0.0837 | 0.0137 | 0.909 | 5.08e-03 | 212.8 | 26.7 | 4→4.8 | 280.34 | 474.0 |
|
|
105
|
+
| ionosphere | torch MLP | 0.1356 | 0.0128 | 0.908 | 1.57e-02 | 577.0 | 0.0 | 2→2.0 | 46.46 | 66.3 |
|
|
106
|
+
| ionosphere | torch MLP, scaled | 0.1149 | 0.0224 | 0.887 | 8.43e-03 | 167.8 | 17.1 | 2→3.0 | 82.35 | 88.1 |
|
|
107
|
+
<!-- /board:per-sample -->
|
|
108
|
+
|
|
109
|
+
### Is the port faithful?
|
|
110
|
+
|
|
111
|
+
Each C-exact torch configuration against the C model it ports. Expect
|
|
112
|
+
*within noise* everywhere: the rules are the same, only the initialisation
|
|
113
|
+
RNG differs.
|
|
114
|
+
|
|
115
|
+
<!-- board:port -->
|
|
116
|
+
| task | comparison | Δ hold MSE | 2se | verdict | params ratio |
|
|
117
|
+
|---|---|---:|---:|---|---:|
|
|
118
|
+
| xor | torch ref-type-nn vs C type-nn | n/a | n/a | train acc 1.00 vs 0.97 (no hold-out) | 1.05× |
|
|
119
|
+
| xor | torch ref-type-nn-overfit vs C type-nn-overfit | n/a | n/a | both fit (no hold-out) | 0.71× |
|
|
120
|
+
| iris | torch ref-type-nn vs C type-nn | +0.0082 | 0.0128 | higher, within noise | 1.20× |
|
|
121
|
+
| iris | torch ref-type-nn-overfit vs C type-nn-overfit | +0.0031 | 0.0036 | higher, within noise | 0.96× |
|
|
122
|
+
| wine | torch ref-type-nn vs C type-nn | -0.0043 | 0.0068 | lower, within noise | 1.17× |
|
|
123
|
+
| wine | torch ref-type-nn-overfit vs C type-nn-overfit | +0.0106 | 0.0103 | higher, **clear** | 0.94× |
|
|
124
|
+
| wdbc | torch ref-type-nn vs C type-nn | -0.0048 | 0.0059 | lower, within noise | 1.15× |
|
|
125
|
+
| wdbc | torch ref-type-nn-overfit vs C type-nn-overfit | -0.0036 | 0.0059 | lower, within noise | 1.05× |
|
|
126
|
+
| diabetes | torch ref-type-nn vs C type-nn | -0.0003 | 0.0007 | lower, within noise | 0.93× |
|
|
127
|
+
| diabetes | torch ref-type-nn-overfit vs C type-nn-overfit | -0.0008 | 0.0028 | lower, within noise | 0.96× |
|
|
128
|
+
| ionosphere | torch ref-type-nn vs C type-nn | -0.0014 | 0.0199 | lower, within noise | 1.07× |
|
|
129
|
+
| ionosphere | torch ref-type-nn-overfit vs C type-nn-overfit | +0.0089 | 0.0190 | higher, within noise | 0.95× |
|
|
130
|
+
<!-- /board:port -->
|
|
131
|
+
|
|
132
|
+
### Like for like: one harness, one framework
|
|
133
|
+
|
|
134
|
+
The comparisons that answer "does the architecture help?". Both sides run in
|
|
135
|
+
the same Python harness with the same data, protocol and seeds; only the
|
|
136
|
+
model differs. (The pre-audit board compared torch type-nn with C's MLP
|
|
137
|
+
instead, which mixes the framework and RNG into the comparison.)
|
|
138
|
+
|
|
139
|
+
<!-- board:like-for-like -->
|
|
140
|
+
| task | comparison | Δ hold MSE | 2se | verdict | params ratio |
|
|
141
|
+
|---|---|---:|---:|---|---:|
|
|
142
|
+
| xor | **torch type-nn** vs torch MLP | n/a | n/a | both fit (no hold-out) | 1.91× |
|
|
143
|
+
| xor | torch type-nn-bic vs torch MLP | n/a | n/a | both fit (no hold-out) | 0.75× |
|
|
144
|
+
| xor | **torch type-nn** vs torch type-nn-bic | n/a | n/a | both fit (no hold-out) | 2.54× |
|
|
145
|
+
| xor | torch MLP, scaled vs torch MLP | n/a | n/a | train acc 0.90 vs 1.00 (no hold-out) | 1.18× |
|
|
146
|
+
| iris | **torch type-nn** vs torch MLP | -0.0051 | 0.0042 | lower, **clear** | 2.69× |
|
|
147
|
+
| iris | torch type-nn-bic vs torch MLP | -0.0059 | 0.0067 | lower, within noise | 2.01× |
|
|
148
|
+
| iris | **torch type-nn** vs torch type-nn-bic | +0.0008 | 0.0058 | higher, within noise | 1.34× |
|
|
149
|
+
| iris | torch MLP, scaled vs torch MLP | -0.0047 | 0.0055 | lower, within noise | 1.07× |
|
|
150
|
+
| wine | **torch type-nn** vs torch MLP | -0.0070 | 0.0066 | lower, **clear** | 1.77× |
|
|
151
|
+
| wine | torch type-nn-bic vs torch MLP | +0.0006 | 0.0070 | higher, within noise | 0.71× |
|
|
152
|
+
| wine | **torch type-nn** vs torch type-nn-bic | -0.0075 | 0.0087 | lower, within noise | 2.50× |
|
|
153
|
+
| wine | torch MLP, scaled vs torch MLP | -0.0082 | 0.0075 | lower, **clear** | 0.45× |
|
|
154
|
+
| wdbc | **torch type-nn** vs torch MLP | +0.0001 | 0.0049 | higher, within noise | 0.43× |
|
|
155
|
+
| wdbc | torch type-nn-bic vs torch MLP | -0.0036 | 0.0058 | lower, within noise | 0.41× |
|
|
156
|
+
| wdbc | **torch type-nn** vs torch type-nn-bic | +0.0037 | 0.0057 | higher, within noise | 1.05× |
|
|
157
|
+
| wdbc | torch MLP, scaled vs torch MLP | +0.0067 | 0.0079 | higher, within noise | 0.26× |
|
|
158
|
+
| diabetes | **torch type-nn** vs torch MLP | +0.0023 | 0.0134 | higher, within noise | 0.52× |
|
|
159
|
+
| diabetes | torch type-nn-bic vs torch MLP | -0.0147 | 0.0034 | lower, **clear** | 0.09× |
|
|
160
|
+
| diabetes | **torch type-nn** vs torch type-nn-bic | +0.0171 | 0.0130 | higher, **clear** | 5.56× |
|
|
161
|
+
| diabetes | torch MLP, scaled vs torch MLP | -0.0067 | 0.0037 | lower, **clear** | 0.32× |
|
|
162
|
+
| ionosphere | **torch type-nn** vs torch MLP | -0.0502 | 0.0219 | lower, **clear** | 0.68× |
|
|
163
|
+
| ionosphere | torch type-nn-bic vs torch MLP | -0.0519 | 0.0167 | lower, **clear** | 0.37× |
|
|
164
|
+
| ionosphere | **torch type-nn** vs torch type-nn-bic | +0.0017 | 0.0224 | higher, within noise | 1.83× |
|
|
165
|
+
| ionosphere | torch MLP, scaled vs torch MLP | -0.0208 | 0.0231 | lower, within noise | 0.29× |
|
|
166
|
+
<!-- /board:like-for-like -->
|
|
167
|
+
|
|
168
|
+
### The C board, for reference
|
|
169
|
+
|
|
170
|
+
<!-- board:c-board -->
|
|
171
|
+
| task | comparison | Δ hold MSE | 2se | verdict | params ratio |
|
|
172
|
+
|---|---|---:|---:|---|---:|
|
|
173
|
+
| xor | C type-nn vs C c-mlp | n/a | n/a | train acc 0.97 vs 0.99 (no hold-out) | 0.72× |
|
|
174
|
+
| xor | C type-nn-overfit vs C c-mlp | n/a | n/a | train acc 1.00 vs 0.99 (no hold-out) | 2.39× |
|
|
175
|
+
| iris | C type-nn vs C c-mlp | -0.0054 | 0.0028 | lower, **clear** | 1.79× |
|
|
176
|
+
| iris | C type-nn-overfit vs C c-mlp | -0.0061 | 0.0027 | lower, **clear** | 2.48× |
|
|
177
|
+
| wine | C type-nn vs C c-mlp | -0.0002 | 0.0030 | lower, within noise | 0.67× |
|
|
178
|
+
| wine | C type-nn-overfit vs C c-mlp | +0.0015 | 0.0039 | higher, within noise | 1.37× |
|
|
179
|
+
| wdbc | C type-nn vs C c-mlp | +0.0028 | 0.0034 | higher, within noise | 0.30× |
|
|
180
|
+
| wdbc | C type-nn-overfit vs C c-mlp | +0.0012 | 0.0030 | higher, within noise | 0.34× |
|
|
181
|
+
| diabetes | C type-nn vs C c-mlp | -0.0145 | 0.0015 | lower, **clear** | 0.10× |
|
|
182
|
+
| diabetes | C type-nn-overfit vs C c-mlp | -0.0101 | 0.0017 | lower, **clear** | 0.17× |
|
|
183
|
+
| ionosphere | C type-nn vs C c-mlp | -0.0480 | 0.0087 | lower, **clear** | 0.25× |
|
|
184
|
+
| ionosphere | C type-nn-overfit vs C c-mlp | -0.0601 | 0.0092 | lower, **clear** | 0.58× |
|
|
185
|
+
<!-- /board:c-board -->
|
|
186
|
+
|
|
187
|
+
On the C board both rules are clearly better than the MLP on diabetes and
|
|
188
|
+
ionosphere with fewer parameters, and within noise on iris, wine and wdbc.
|
|
189
|
+
|
|
190
|
+
## Larger data
|
|
191
|
+
|
|
192
|
+
`benchmarks/scale.py`, mini-batches of 32, Adam at 0.003 (TypeAdam for
|
|
193
|
+
type-nn, stock Adam otherwise), 5 seeds, the same 70/30 xorshift split.
|
|
194
|
+
|
|
195
|
+
- **digits**: UCI optical digits as shipped in scikit-learn 1.5.2, 1797 × 64
|
|
196
|
+
pixels, 10 classes (one-hot MSE), 60 epochs.
|
|
197
|
+
- **friedman**: Friedman #1, the fixed Delve / OpenML 564 "fried" file,
|
|
198
|
+
40 768 × 10 inputs (5 pure noise), 40 epochs. The target is min-max scaled
|
|
199
|
+
on the training rows, so with `Var(e) = 1` the best reachable hold-out MSE
|
|
200
|
+
is `1 / span²`; it is computed from the data and reported as the noise floor.
|
|
201
|
+
|
|
202
|
+
The type-nn and scaled-MLP models are built by the same constructor as the
|
|
203
|
+
per-sample board (`bench.build`); MLP-16 and MLP-64 are fixed one-hidden-layer
|
|
204
|
+
MLPs.
|
|
205
|
+
|
|
206
|
+
<!-- board:scale -->
|
|
207
|
+
Generated by `benchmarks/board.py` from `benchmarks/results/scale-batch32.jsonl` (5 seeds, batch 32, cpu).
|
|
208
|
+
|
|
209
|
+
| task | model | hold MSE | ± sd | × noise floor | hold acc | train MSE | params | layers | train s |
|
|
210
|
+
|---|---|---:|---:|---:|---:|---:|---:|---:|---:|
|
|
211
|
+
| digits | **type-nn** | 0.00620 | 0.00131 | n/a | 0.963 | 0.00023 | 5509 | 7.0 | 38.0 |
|
|
212
|
+
| digits | type-nn-bic | 0.00696 | 0.00028 | n/a | 0.957 | 0.00014 | 3352 | 7.0 | 202.5 |
|
|
213
|
+
| digits | MLP, scaled | 0.00779 | 0.00053 | n/a | 0.964 | 0.00170 | 1323 | 3.0 | 9.0 |
|
|
214
|
+
| digits | MLP-16 | 0.01110 | 0.00074 | n/a | 0.959 | 0.00490 | 1210 | 2.0 | 3.7 |
|
|
215
|
+
| digits | MLP-64 | 0.01089 | 0.00022 | n/a | 0.974 | 0.00343 | 4810 | 2.0 | 4.7 |
|
|
216
|
+
| friedman | **type-nn** | 0.00660 | 0.00037 | 6.65 | n/a | 0.00666 | 18 | 2.0 | 211.7 |
|
|
217
|
+
| friedman | type-nn-bic | 0.00195 | 0.00089 | 1.97 | n/a | 0.00194 | 80 | 3.0 | 254.0 |
|
|
218
|
+
| friedman | MLP, scaled | 0.01377 | 0.01039 | 13.88 | n/a | 0.01362 | 17 | 2.6 | 93.0 |
|
|
219
|
+
| friedman | MLP-16 | 0.00121 | 0.00013 | 1.22 | n/a | 0.00118 | 193 | 2.0 | 56.3 |
|
|
220
|
+
| friedman | MLP-64 | 0.00114 | 0.00005 | 1.15 | n/a | 0.00110 | 769 | 2.0 | 43.2 |
|
|
221
|
+
|
|
222
|
+
friedman: noise floor (best reachable hold-out MSE, `1 / span²`) = 0.00099
|
|
223
|
+
|
|
224
|
+
| task | comparison | Δ hold MSE | 2se | verdict | params ratio |
|
|
225
|
+
|---|---|---:|---:|---|---:|
|
|
226
|
+
| digits | **type-nn** vs MLP-16 | -0.0049 | 0.0013 | lower, **clear** | 4.55× |
|
|
227
|
+
| digits | **type-nn** vs MLP-64 | -0.0047 | 0.0012 | lower, **clear** | 1.15× |
|
|
228
|
+
| digits | type-nn-bic vs MLP-64 | -0.0039 | 0.0003 | lower, **clear** | 0.70× |
|
|
229
|
+
| digits | **type-nn** vs type-nn-bic | -0.0008 | 0.0012 | lower, within noise | 1.64× |
|
|
230
|
+
| digits | MLP, scaled vs MLP-16 | -0.0033 | 0.0008 | lower, **clear** | 1.09× |
|
|
231
|
+
| friedman | **type-nn** vs MLP-16 | +0.0054 | 0.0004 | higher, **clear** | 0.09× |
|
|
232
|
+
| friedman | **type-nn** vs MLP-64 | +0.0055 | 0.0003 | higher, **clear** | 0.02× |
|
|
233
|
+
| friedman | type-nn-bic vs MLP-64 | +0.0008 | 0.0008 | higher, **clear** | 0.10× |
|
|
234
|
+
| friedman | **type-nn** vs type-nn-bic | +0.0046 | 0.0009 | higher, **clear** | 0.22× |
|
|
235
|
+
| friedman | MLP, scaled vs MLP-16 | +0.0126 | 0.0093 | higher, **clear** | 0.09× |
|
|
236
|
+
<!-- /board:scale -->
|
|
237
|
+
|
|
238
|
+
## Notes on honesty
|
|
239
|
+
|
|
240
|
+
- **Width on every junction** is the default because the architecture says a
|
|
241
|
+
width probe grows between any two layers, not because of any result. It
|
|
242
|
+
used to be an opt-in (`type-nn-through`) introduced after Friedman #1
|
|
243
|
+
results under-grew, and was then judged on the same Friedman hold-out; that
|
|
244
|
+
selection effect is why its old "best model on the task" claim is not
|
|
245
|
+
repeated here. The C width rule (width blocked next to the depth probe)
|
|
246
|
+
remains available as `width_through_depth_probe=False` and is what the
|
|
247
|
+
`ref-*` rows use.
|
|
248
|
+
- **The threshold rule is the default** for the same reason: the architecture
|
|
249
|
+
decides every scaling step from what back-prop did to the dummy weights.
|
|
250
|
+
`rule="bic"` adds the BIC prior, which re-evaluates training pairs with
|
|
251
|
+
forward passes; both are on the board.
|
|
252
|
+
- **Pre-audit results** are archived in `benchmarks/results/pre-audit/` with
|
|
253
|
+
what changed since; they are not comparable and are not read by `board.py`.
|
|
254
|
+
- **Task settings** (epochs, lr) are the C board's for the per-sample board
|
|
255
|
+
and `scale.py`'s constants for the larger data; they are shared by every
|
|
256
|
+
model and were not tuned per model.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 torch-type-nn contributors
|
|
4
|
+
Portions derived from type-nn, Copyright (c) 2026 Hadi Lashkati Ghouchani
|
|
5
|
+
(https://github.com/hadilq/type-nn), used under the MIT License.
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: torch-type-nn
|
|
3
|
+
Version: 0.1.0.dev0
|
|
4
|
+
Summary: type-nn for PyTorch: networks of partition functions (And of Ors) that grow and prune their own width, degree and depth
|
|
5
|
+
Project-URL: Homepage, https://github.com/hadilq/torch-type-nn
|
|
6
|
+
Project-URL: Repository, https://github.com/hadilq/torch-type-nn
|
|
7
|
+
Project-URL: Issues, https://github.com/hadilq/torch-type-nn/issues
|
|
8
|
+
Project-URL: Board, https://github.com/hadilq/torch-type-nn/blob/main/BOARD.md
|
|
9
|
+
Project-URL: C reference, https://github.com/hadilq/type-nn
|
|
10
|
+
Project-URL: Design notes, https://hadilq.com/posts/train-the-knowledge/
|
|
11
|
+
Author: torch-type-nn contributors
|
|
12
|
+
License-Expression: MIT
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Keywords: neural-network,partition-function,pruning,pytorch,structure-learning,type-theory
|
|
15
|
+
Classifier: Development Status :: 3 - Alpha
|
|
16
|
+
Classifier: Intended Audience :: Science/Research
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: torch>=2.2
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: build; extra == 'dev'
|
|
23
|
+
Requires-Dist: numpy; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
26
|
+
Requires-Dist: twine; extra == 'dev'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# torch-type-nn
|
|
30
|
+
|
|
31
|
+
A PyTorch port of [type-nn](https://github.com/hadilq/type-nn): a neural
|
|
32
|
+
network whose layers are partition functions of an And over Ors, and which
|
|
33
|
+
grows and prunes its own width, degree and depth while it trains. The
|
|
34
|
+
design is explained in
|
|
35
|
+
[Train the knowledge](https://hadilq.com/posts/train-the-knowledge/).
|
|
36
|
+
|
|
37
|
+
> Status: alpha (0.1.0.dev0). Forward, gradients and the per-Or Adam step
|
|
38
|
+
> agree with the C reference to 1e-12, and both C scaling rules have exact
|
|
39
|
+
> ports on the board (see [Validation](#validation)). Deliberate differences
|
|
40
|
+
> from C are listed in [docs/DIFFERENCES.md](https://github.com/hadilq/torch-type-nn/blob/main/docs/DIFFERENCES.md); the audit
|
|
41
|
+
> that led to the current defaults is in [docs/AUDIT.md](https://github.com/hadilq/torch-type-nn/blob/main/docs/AUDIT.md).
|
|
42
|
+
|
|
43
|
+
## Quick start
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import torch
|
|
47
|
+
from torch_type_nn import TypeNN, fit
|
|
48
|
+
|
|
49
|
+
model = TypeNN(in_features=4, out_features=3) # birth: round(ln(1 + n m)) layers
|
|
50
|
+
result = fit(model, X, Y, epochs=250, lr=0.005) # grows and prunes while it trains
|
|
51
|
+
print(model.structure(), model.num_params(), result.counters)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`fit` reproduces type-nn's per-sample protocol by default
|
|
55
|
+
(`batch_size=1`); pass `batch_size=64` (or more) for the batched, GPU-friendly
|
|
56
|
+
path. There is no width, degree or depth to choose.
|
|
57
|
+
|
|
58
|
+
For your own training loop, drive the structure with a `StructureScaler`:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from torch_type_nn import TypeNN, TypeAdam, StructureScaler
|
|
62
|
+
|
|
63
|
+
model = TypeNN(n, m)
|
|
64
|
+
opt = TypeAdam(model, lr=lr)
|
|
65
|
+
scaler = StructureScaler(model, opt, epochs=E, steps_per_epoch=len(loader))
|
|
66
|
+
scaler.begin()
|
|
67
|
+
for epoch in range(E):
|
|
68
|
+
for x, t in loader:
|
|
69
|
+
y = model(x)
|
|
70
|
+
loss = 0.5 * (y - t).pow(2).mean()
|
|
71
|
+
opt.zero_grad(); loss.backward(); opt.step()
|
|
72
|
+
scaler.observe(x, y, t) # residual statistics (and pairs, for rule="bic")
|
|
73
|
+
scaler.epoch_end() # grow / prune at the boundary
|
|
74
|
+
scaler.end() # last prune; no probe survives
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Structure learning
|
|
78
|
+
|
|
79
|
+
One dummy rule on three axes. A **probe** is an identity that back-prop is
|
|
80
|
+
free to move; a probe that back-prop moved past `theta(T) = lr T^(3/4)`
|
|
81
|
+
(`T` its age in optimizer steps) is promoted, and a fresh probe takes its
|
|
82
|
+
place. All scaling up happens early and all scaling down late.
|
|
83
|
+
|
|
84
|
+
| axis | probe (grow, `u < 1/3`) | drop (prune, `u >= 2/3`) |
|
|
85
|
+
| ------ | ----------------------------------------------------------------- | ---------------------------------------------- |
|
|
86
|
+
| width (Or) | the previous layer grows a trained unit; the current layer reads it with a dummy weight of exactly 0 | the coordinate, when its weights are back inside the band (a junction keeps its most-moved one) |
|
|
87
|
+
| degree (And)| each And carries one noisy identity Or (w ~ 0, b ~ 1) | an Or back inside the band (an And keeps its most-moved Or: of two identity Ors one goes) |
|
|
88
|
+
| depth | an identity layer in the gap, between *any* two layers, with the largest mean `|dL/dx|` | a layer back to identity within the band (one per boundary; folded) |
|
|
89
|
+
|
|
90
|
+
(`u = step / total`; the band is `theta_band = lr S^(3/4)`, `S` optimizer
|
|
91
|
+
steps per epoch.) Growth is gated on an unexplained residual
|
|
92
|
+
(`MSE > Var(t) / N`). Depth edits fold the best affine fit between `x` and
|
|
93
|
+
`F(x)` into the next layer, so they are near-exact. A network is born with
|
|
94
|
+
`round(ln(1 + n m))` layers, `n` inputs and `m` outputs, each layer's output
|
|
95
|
+
feeding the next, as dense as an MLP.
|
|
96
|
+
|
|
97
|
+
**Two rules.** `StructureScaler(..., rule=...)`:
|
|
98
|
+
|
|
99
|
+
- `"threshold"` (default) is the architecture: every decision comes from
|
|
100
|
+
what back-prop did to the dummy weights; no training pair is stored or
|
|
101
|
+
re-run. It is the rule of C's `type-nn-overfit`.
|
|
102
|
+
- `"bic"` adds the Bayesian-information prior of the post: a moved probe is
|
|
103
|
+
promoted only if it also pays `n ln(MSE_without / MSE_with) > k ln n`,
|
|
104
|
+
where `MSE_without` is *measured* by resetting the item to its identity
|
|
105
|
+
and re-running the epoch's training pairs (so the scaler keeps one epoch
|
|
106
|
+
of `(x, t)` in memory), and pruning ablates items while the criterion
|
|
107
|
+
`n ln MSE + K ln n` allows. It is the rule of C's `type-nn`.
|
|
108
|
+
|
|
109
|
+
**Width on every junction.** By default a width probe also grows on a
|
|
110
|
+
junction that crosses the depth probe (carried through the probe layer).
|
|
111
|
+
C blocks width there, which leaves a network born with depth ≤ 2 no width
|
|
112
|
+
site while a depth probe exists; `TypeNNAdapter(model,
|
|
113
|
+
width_through_depth_probe=False)` reproduces C.
|
|
114
|
+
|
|
115
|
+
Both rules and both width rules are on [BOARD.md](https://github.com/hadilq/torch-type-nn/blob/main/BOARD.md), including exact
|
|
116
|
+
ports of the two C models.
|
|
117
|
+
|
|
118
|
+
**Any architecture, any optimizer.** The scaler is written against a
|
|
119
|
+
small protocol (`torch_type_nn.protocol.Scalable`): the rule lives in
|
|
120
|
+
`StructureScaler`, and what an item *is* (how to insert an identity, measure
|
|
121
|
+
its distance from it, remove it) comes from an adapter. `TypeNN` is wrapped
|
|
122
|
+
in `TypeNNAdapter` automatically. `ScalableMLP` (`Linear`/ReLU stacks) is
|
|
123
|
+
the second family: `MLPAdapter` grows and prunes its width and depth. See
|
|
124
|
+
[docs/ADAPTERS.md](https://github.com/hadilq/torch-type-nn/blob/main/docs/ADAPTERS.md) for writing an adapter. Stock
|
|
125
|
+
`torch.optim` optimizers work too: structural edits are reported as `Edit`s
|
|
126
|
+
with index maps, `follow_structure` remaps the optimizer state through them,
|
|
127
|
+
and the scaler attaches `keep_invariants` so `a >= 1` holds after every step.
|
|
128
|
+
|
|
129
|
+
## The layer
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
import torch
|
|
133
|
+
from torch_type_nn import AndOr, TypeAdam
|
|
134
|
+
|
|
135
|
+
layer = AndOr(in_features=8, out_features=4, ors_per_unit=2)
|
|
136
|
+
z = layer(torch.randn(32, 8)) # (32, 4)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Unit k of a layer is one And over its Ors r:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
Or_kr = w_kr . x + b_kr sum type
|
|
143
|
+
A_k = prod_r Or_kr ^ a_kr product type, a_kr >= 1
|
|
144
|
+
z_k = sign(A_k) ln(1 + |A_k|) partition function
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- **Batched and padded.** All Ors of a layer live in one `(m, R, n)`
|
|
148
|
+
tensor; a unit with fewer than `R` Ors has identity Ors (w = 0, b = 1,
|
|
149
|
+
a = 1) in its free slots. The identity is exactly 1, so padding never
|
|
150
|
+
changes the output, and its gradients are masked so no optimizer moves
|
|
151
|
+
it. This is what gives type-nn batching and GPU support.
|
|
152
|
+
- **Exact backward.** `AndOrFunction` computes the gradients in log
|
|
153
|
+
space, including the one-sided rule at a factor that is exactly 0
|
|
154
|
+
(cofactor slope when a = 1, flat when a > 1), where autograd through
|
|
155
|
+
`log|O|` would give NaN.
|
|
156
|
+
- **Per-Or Adam.** `TypeAdam(model, lr)` gives every Or its own step
|
|
157
|
+
count, so an Or born late gets the same bias correction as one born at
|
|
158
|
+
the start, and keeps `a >= 1`. It takes the module, not a parameter list,
|
|
159
|
+
because structural edits replace the parameter tensors; the Adam moments
|
|
160
|
+
live in the layer (`adam_*` buffers) so edits move them with the
|
|
161
|
+
weights. Non-type-nn parameters in the same model get ordinary Adam.
|
|
162
|
+
Stock `torch.optim` optimizers also work on a layer of fixed structure.
|
|
163
|
+
- **Structural primitives.** `add_or`, `drop_or`, `add_unit`,
|
|
164
|
+
`drop_unit`, `add_input` (a zero column: function preserved exactly),
|
|
165
|
+
`drop_input`, `compact`. `load_state_dict` accepts a checkpoint of a
|
|
166
|
+
different structure.
|
|
167
|
+
|
|
168
|
+
## Validation
|
|
169
|
+
|
|
170
|
+
Two independent checks against [hadilq/type-nn](https://github.com/hadilq/type-nn):
|
|
171
|
+
|
|
172
|
+
- **Number for number.** `tests/test_crosscheck_c.py` compiles the C code
|
|
173
|
+
(`tests/c/crosscheck.c`), builds the same ragged network in both, and
|
|
174
|
+
compares the forward pass, dL/dx, every dL/dw, dL/db and dL/da, and the
|
|
175
|
+
weights after two per-Or Adam steps. Everything agrees to 1e-12.
|
|
176
|
+
`nix flake check` runs it (the flake pins the C source as an input);
|
|
177
|
+
locally, `TYPE_NN_SRC=/path/to/type-nn pytest`.
|
|
178
|
+
- **The board.** `benchmarks/bench.py` ports `bench.c` exactly: the same
|
|
179
|
+
files, the same xorshift split and shuffle (checked row for row), per-sample
|
|
180
|
+
Adam at `lr x 0.1`, the same epochs and learning rates, and 5 seeds per
|
|
181
|
+
cell. `ref-type-nn` and `ref-type-nn-overfit` are exact configurations of
|
|
182
|
+
C's two models; `benchmarks/board.py` compares each with the C board
|
|
183
|
+
(`benchmarks/reference/`, deterministic, reproduced digit for digit) and
|
|
184
|
+
writes every table of [BOARD.md](https://github.com/hadilq/torch-type-nn/blob/main/BOARD.md) from the result files.
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
nix run .#board-all # every result on all cores, then BOARD.md's tables
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Development
|
|
191
|
+
|
|
192
|
+
Everything is wired through `flake.nix`:
|
|
193
|
+
|
|
194
|
+
```sh
|
|
195
|
+
nix develop # python + torch + pytest + build tools (or: direnv allow);
|
|
196
|
+
# also copies the pinned datasets to benchmarks/data
|
|
197
|
+
pytest # run the tests from the checkout
|
|
198
|
+
nix run .#test # same, without entering the shell
|
|
199
|
+
nix flake check # build the package, run pytest inside the build (with the
|
|
200
|
+
# pinned datasets), ruff
|
|
201
|
+
nix build # ./result: the installed package
|
|
202
|
+
nix run .#dist # sdist + wheel in ./dist, checked by twine
|
|
203
|
+
nix run .#publish -- --repository testpypi # upload (TestPyPI first)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The benchmark datasets are pinned (URL + SRI hash) in
|
|
207
|
+
`benchmarks/datasets.json` and never committed; see
|
|
208
|
+
[benchmarks/data/README.md](https://github.com/hadilq/torch-type-nn/blob/main/benchmarks/data/README.md).
|
|
209
|
+
|
|
210
|
+
Without Nix: `pip install -e ".[dev]" && python benchmarks/fetch_data.py && pytest`.
|
|
211
|
+
|
|
212
|
+
### On a GPU
|
|
213
|
+
|
|
214
|
+
Every test that takes the `device` fixture also runs on `cuda` when a CUDA
|
|
215
|
+
device is visible; `TNN_REQUIRE_CUDA=1` makes a missing GPU an error rather
|
|
216
|
+
than a skip. One script runs the GPU suite (the whole test-suite with
|
|
217
|
+
`TNN_REQUIRE_CUDA=1`, then the board's training loop for every model on the
|
|
218
|
+
device), in two ways:
|
|
219
|
+
|
|
220
|
+
```sh
|
|
221
|
+
nix run .#test-cuda # on the host, now: no Nix configuration needed
|
|
222
|
+
nix build .#cuda-tests -L # in the build sandbox: needs the `cuda` feature
|
|
223
|
+
nix flake check --impure # adds checks.cuda when /dev/nvidiactl exists
|
|
224
|
+
TORCH_TYPE_NN_CUDA=1 nix flake check --impure # force it (=0: leave it out)
|
|
225
|
+
nix develop .#cuda # torch-bin (CUDA 13.0) shell
|
|
226
|
+
nix run .#bench-cuda -- all all --seeds 5 --device cuda
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
A plain `nix flake check` is a pure evaluation, which cannot see the host, so
|
|
230
|
+
it never adds the GPU check. With `--impure` it adds `checks.cuda` when a GPU
|
|
231
|
+
is visible (`/dev/nvidiactl`) *and* the Nix daemon enables the `cuda` system
|
|
232
|
+
feature (read from `/etc/nix/nix.conf`, `/etc/nix/nix.custom.conf`,
|
|
233
|
+
`NIX_CONFIG`); a GPU without the feature skips the check with a warning.
|
|
234
|
+
`TORCH_TYPE_NN_CUDA=1` / `=0` forces it on / off.
|
|
235
|
+
|
|
236
|
+
The sandboxed check declares `requiredSystemFeatures = [ "cuda" ]`, so the
|
|
237
|
+
daemon must advertise the feature and expose the GPU to the build:
|
|
238
|
+
|
|
239
|
+
**NixOS:**
|
|
240
|
+
|
|
241
|
+
```nix
|
|
242
|
+
programs.nix-required-mounts = {
|
|
243
|
+
enable = true;
|
|
244
|
+
presets.nvidia-gpu.enable = true; # adds the cuda/gpu/opengl features and
|
|
245
|
+
}; # mounts the driver + /dev/nvidia* for them
|
|
246
|
+
# Setting nix.settings.system-features replaces the default list, so keep the
|
|
247
|
+
# features you rely on (nixos-test, benchmark, big-parallel, kvm, ...):
|
|
248
|
+
nix.settings.system-features = [ "nixos-test" "benchmark" "big-parallel" "kvm" ];
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Other Linux (Nix daemon):** find the directory holding the driver's
|
|
252
|
+
`libcuda.so.1` (`ldconfig -p | grep libcuda.so.1`; e.g. `/usr/lib/x86_64-linux-gnu`
|
|
253
|
+
on Debian/Ubuntu, `/usr/lib` on Arch), then in `/etc/nix/nix.conf`:
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
extra-system-features = cuda
|
|
257
|
+
extra-sandbox-paths = /dev/nvidia0 /dev/nvidiactl /dev/nvidia-uvm /dev/nvidia-uvm-tools? /run/opengl-driver/lib=/usr/lib/x86_64-linux-gnu
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
and restart the daemon (`sudo systemctl restart nix-daemon`). torch-bin looks
|
|
261
|
+
for the driver in `/run/opengl-driver/lib`; the `target=source` form mounts the
|
|
262
|
+
host's driver directory there inside the sandbox only (a trailing `?` marks a
|
|
263
|
+
path that may be missing). With several GPUs add `/dev/nvidia1`, ... Check with
|
|
264
|
+
`nix build .#cuda-tests -L`.
|
|
265
|
+
|
|
266
|
+
## Publishing
|
|
267
|
+
|
|
268
|
+
Releases go to PyPI from GitHub Actions with trusted publishing
|
|
269
|
+
(`.github/workflows/publish.yml`): no API token is stored anywhere.
|
|
270
|
+
|
|
271
|
+
One-time setup, on PyPI → *Your account* → *Publishing* → *Add a new pending
|
|
272
|
+
publisher* → *GitHub*:
|
|
273
|
+
|
|
274
|
+
| field | value |
|
|
275
|
+
|---|---|
|
|
276
|
+
| PyPI Project Name | `torch-type-nn` |
|
|
277
|
+
| Owner | `hadilq` |
|
|
278
|
+
| Repository name | `torch-type-nn` |
|
|
279
|
+
| Workflow name | `publish.yml` |
|
|
280
|
+
| Environment name | `pypi` |
|
|
281
|
+
|
|
282
|
+
For TestPyPI, the same on test.pypi.org (a separate account) with environment
|
|
283
|
+
`testpypi`. GitHub creates both environments on first use; add protection
|
|
284
|
+
rules (e.g. required reviewers) under *Settings → Environments* if wanted.
|
|
285
|
+
|
|
286
|
+
Release:
|
|
287
|
+
|
|
288
|
+
```sh
|
|
289
|
+
# 1. set the version in pyproject.toml (e.g. 0.1.0), commit, push
|
|
290
|
+
# 2. rehearse: Actions → publish → Run workflow → testpypi
|
|
291
|
+
# 3. release: the tag must equal the version
|
|
292
|
+
git tag v0.1.0 && git push origin v0.1.0
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
The workflow builds the sdist and wheel, runs the test-suite against the
|
|
296
|
+
built wheel (CPU torch), and only then uploads. Locally, `nix run .#dist`
|
|
297
|
+
builds and checks the same files; `nix run .#publish` uploads them with
|
|
298
|
+
twine and a token.
|
|
299
|
+
|
|
300
|
+
## License
|
|
301
|
+
|
|
302
|
+
MIT. Derived from type-nn by Hadi Lashkati Ghouchani (MIT).
|