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.
Files changed (50) hide show
  1. torch_type_nn-0.1.0.dev0/.gitignore +15 -0
  2. torch_type_nn-0.1.0.dev0/BOARD.md +256 -0
  3. torch_type_nn-0.1.0.dev0/LICENSE +23 -0
  4. torch_type_nn-0.1.0.dev0/PKG-INFO +302 -0
  5. torch_type_nn-0.1.0.dev0/README.md +274 -0
  6. torch_type_nn-0.1.0.dev0/benchmarks/bench.py +421 -0
  7. torch_type_nn-0.1.0.dev0/benchmarks/board.py +230 -0
  8. torch_type_nn-0.1.0.dev0/benchmarks/data/README.md +31 -0
  9. torch_type_nn-0.1.0.dev0/benchmarks/datasets.json +26 -0
  10. torch_type_nn-0.1.0.dev0/benchmarks/fetch_data.py +68 -0
  11. torch_type_nn-0.1.0.dev0/benchmarks/reference/c-type-nn-7333bf7-30seeds.jsonl +18 -0
  12. torch_type_nn-0.1.0.dev0/benchmarks/results/board-per-sample.jsonl +36 -0
  13. torch_type_nn-0.1.0.dev0/benchmarks/results/scale-batch32.jsonl +10 -0
  14. torch_type_nn-0.1.0.dev0/benchmarks/scale.py +237 -0
  15. torch_type_nn-0.1.0.dev0/docs/ADAPTERS.md +94 -0
  16. torch_type_nn-0.1.0.dev0/docs/AUDIT.md +63 -0
  17. torch_type_nn-0.1.0.dev0/docs/DIFFERENCES.md +131 -0
  18. torch_type_nn-0.1.0.dev0/flake.lock +78 -0
  19. torch_type_nn-0.1.0.dev0/flake.nix +308 -0
  20. torch_type_nn-0.1.0.dev0/pyproject.toml +53 -0
  21. torch_type_nn-0.1.0.dev0/src/torch_type_nn/__init__.py +38 -0
  22. torch_type_nn-0.1.0.dev0/src/torch_type_nn/adapters/__init__.py +6 -0
  23. torch_type_nn-0.1.0.dev0/src/torch_type_nn/adapters/mlp.py +322 -0
  24. torch_type_nn-0.1.0.dev0/src/torch_type_nn/adapters/typenn.py +546 -0
  25. torch_type_nn-0.1.0.dev0/src/torch_type_nn/edits.py +99 -0
  26. torch_type_nn-0.1.0.dev0/src/torch_type_nn/functional.py +126 -0
  27. torch_type_nn-0.1.0.dev0/src/torch_type_nn/layer.py +354 -0
  28. torch_type_nn-0.1.0.dev0/src/torch_type_nn/mlp.py +251 -0
  29. torch_type_nn-0.1.0.dev0/src/torch_type_nn/network.py +115 -0
  30. torch_type_nn-0.1.0.dev0/src/torch_type_nn/optim.py +122 -0
  31. torch_type_nn-0.1.0.dev0/src/torch_type_nn/protocol.py +148 -0
  32. torch_type_nn-0.1.0.dev0/src/torch_type_nn/py.typed +0 -0
  33. torch_type_nn-0.1.0.dev0/src/torch_type_nn/scaling.py +523 -0
  34. torch_type_nn-0.1.0.dev0/src/torch_type_nn/train.py +97 -0
  35. torch_type_nn-0.1.0.dev0/tests/c/crosscheck.c +91 -0
  36. torch_type_nn-0.1.0.dev0/tests/conftest.py +33 -0
  37. torch_type_nn-0.1.0.dev0/tests/data/iris_prune_boundary.pt +0 -0
  38. torch_type_nn-0.1.0.dev0/tests/helpers.py +62 -0
  39. torch_type_nn-0.1.0.dev0/tests/test_board.py +59 -0
  40. torch_type_nn-0.1.0.dev0/tests/test_crosscheck_c.py +100 -0
  41. torch_type_nn-0.1.0.dev0/tests/test_datasets.py +106 -0
  42. torch_type_nn-0.1.0.dev0/tests/test_device.py +208 -0
  43. torch_type_nn-0.1.0.dev0/tests/test_edits.py +150 -0
  44. torch_type_nn-0.1.0.dev0/tests/test_functional.py +151 -0
  45. torch_type_nn-0.1.0.dev0/tests/test_layer.py +104 -0
  46. torch_type_nn-0.1.0.dev0/tests/test_mlp.py +177 -0
  47. torch_type_nn-0.1.0.dev0/tests/test_optim.py +104 -0
  48. torch_type_nn-0.1.0.dev0/tests/test_package.py +11 -0
  49. torch_type_nn-0.1.0.dev0/tests/test_scaling.py +333 -0
  50. 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).