phanerozoic commited on
Commit
278368f
·
verified ·
1 Parent(s): 5ac74e6

Initial commit: verified MOD-5 threshold circuit

Browse files
Files changed (4) hide show
  1. README.md +175 -0
  2. config.json +30 -0
  3. model.py +119 -0
  4. model.safetensors +3 -0
README.md ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ tags:
4
+ - pytorch
5
+ - safetensors
6
+ - formal-verification
7
+ - coq
8
+ - mod5
9
+ - modular-arithmetic
10
+ - threshold-network
11
+ - neuromorphic
12
+ ---
13
+
14
+ # tiny-mod5-prover
15
+
16
+ Formally verified neural network that computes the MOD-5 function (Hamming weight mod 5) on 8-bit inputs. This repository contains the model artifacts; for proof development and Coq source code, see [mod5-verified](https://github.com/CharlesCNorton/mod5-verified).
17
+
18
+ ## Overview
19
+
20
+ This is a threshold network that computes `mod5(x) = HW(x) mod 5` for 8-bit binary inputs, where HW denotes Hamming weight (number of set bits). The network outputs 0, 1, 2, 3, or 4 corresponding to the five residue classes.
21
+
22
+ **Key properties:**
23
+ - 100% accuracy on all 256 possible inputs
24
+ - Correctness proven in Coq via constructive algebraic proof
25
+ - Weights constrained to integers
26
+ - Heaviside step activation (x >= 0 -> 1, else 0)
27
+ - Part of the verified MOD-m family: {MOD-2, MOD-3, MOD-5, ...}
28
+
29
+ ## Architecture
30
+
31
+ | Layer | Neurons | Function |
32
+ |-------|---------|----------|
33
+ | Input | 8 | Binary input bits |
34
+ | Hidden 1 | 9 | Thermometer encoding (HW >= k) |
35
+ | Hidden 2 | 4 | MOD-5 detection |
36
+ | Output | 5 | Classification (one-hot) |
37
+
38
+ **Total: 18 neurons, 146 parameters**
39
+
40
+ ## Quick Start
41
+
42
+ ```python
43
+ import torch
44
+ from safetensors.torch import load_file
45
+
46
+ weights = load_file('model.safetensors')
47
+
48
+ def forward(x, weights):
49
+ x = x.float()
50
+ x = (x @ weights['layer1.weight'].T + weights['layer1.bias'] >= 0).float()
51
+ x = (x @ weights['layer2.weight'].T + weights['layer2.bias'] >= 0).float()
52
+ out = x @ weights['output.weight'].T + weights['output.bias']
53
+ return out.argmax(dim=-1)
54
+
55
+ inputs = torch.tensor([[1, 0, 1, 1, 1, 0, 0, 0]], dtype=torch.float32)
56
+ output = forward(inputs, weights)
57
+ print(f"MOD-5 of [1,0,1,1,1,0,0,0]: {output.item()}") # 4 (4 bits set, 4 mod 5 = 4)
58
+ ```
59
+
60
+ ## Weight Structure
61
+
62
+ | Tensor | Shape | Values | Description |
63
+ |--------|-------|--------|-------------|
64
+ | `layer1.weight` | [9, 8] | All 1s | Thermometer encoding |
65
+ | `layer1.bias` | [9] | [0, -1, ..., -8] | Threshold at HW >= k |
66
+ | `layer2.weight` | [4, 9] | [0,1,1,1,1,-4,1,1,1] | MOD-5 detection |
67
+ | `layer2.bias` | [4] | [-1, -2, -3, -4] | Class thresholds |
68
+ | `output.weight` | [5, 4] | Various | Classification |
69
+ | `output.bias` | [5] | [0, -1, -1, -1, -1] | Output thresholds |
70
+
71
+ ## Algebraic Insight
72
+
73
+ The MOD-m construction uses weights `(1, 1, ..., 1, 1-m)` with `m-1` ones before the reset term.
74
+
75
+ For MOD-5, the pattern `(1, 1, 1, 1, -4)` produces cumulative sums that cycle through `(0, 1, 2, 3, 4, 0, 1, 2, 3, 4, ...)`:
76
+
77
+ ```
78
+ HW=0: cumsum = 0 -> 0 mod 5
79
+ HW=1: cumsum = 1 -> 1 mod 5
80
+ HW=2: cumsum = 2 -> 2 mod 5
81
+ HW=3: cumsum = 3 -> 3 mod 5
82
+ HW=4: cumsum = 4 -> 4 mod 5
83
+ HW=5: cumsum = 0 -> 0 mod 5 (reset: 1+1+1+1-4=0)
84
+ HW=6: cumsum = 1 -> 1 mod 5
85
+ HW=7: cumsum = 2 -> 2 mod 5
86
+ HW=8: cumsum = 3 -> 3 mod 5
87
+ ```
88
+
89
+ ## Formal Verification
90
+
91
+ The network is proven correct in the Coq proof assistant with three independent proofs:
92
+
93
+ **1. Exhaustive verification:**
94
+ ```coq
95
+ Theorem network_correct_exhaustive : verify_all = true.
96
+ Proof. vm_compute. reflexivity. Qed.
97
+ ```
98
+
99
+ **2. Constructive verification (case analysis):**
100
+ ```coq
101
+ Theorem network_correct_constructive : forall x0 x1 x2 x3 x4 x5 x6 x7,
102
+ predict [x0; x1; x2; x3; x4; x5; x6; x7] =
103
+ mod5 [x0; x1; x2; x3; x4; x5; x6; x7].
104
+ ```
105
+
106
+ **3. Algebraic verification:**
107
+ ```coq
108
+ Theorem cumsum_eq_mod5 : forall k,
109
+ (k <= 8)%nat -> cumsum k = Z.of_nat (Nat.modulo k 5).
110
+
111
+ Theorem network_algebraic_correct : forall h,
112
+ (h <= 8)%nat ->
113
+ classify ... = Nat.modulo h 5.
114
+ ```
115
+
116
+ All proofs are axiom-free ("Closed under the global context").
117
+
118
+ ## MOD-5 Distribution
119
+
120
+ For 8-bit inputs (256 total):
121
+
122
+ | Class | Count | Hamming Weights |
123
+ |-------|-------|-----------------|
124
+ | 0 | 57 | 0, 5 |
125
+ | 1 | 36 | 1, 6 |
126
+ | 2 | 36 | 2, 7 |
127
+ | 3 | 57 | 3, 8 |
128
+ | 4 | 70 | 4 |
129
+
130
+ ## The MOD-m Family
131
+
132
+ | Model | Function | Neurons | Params | Weight Pattern |
133
+ |-------|----------|---------|--------|----------------|
134
+ | tiny-parity-prover | MOD-2 | 14 | 139 | (1, -1) |
135
+ | tiny-mod3-prover | MOD-3 | 14 | 110 | (1, 1, -2) |
136
+ | **tiny-mod5-prover** | MOD-5 | 18 | 146 | (1, 1, 1, 1, -4) |
137
+
138
+ ## Limitations
139
+
140
+ - **Fixed input size**: 8 bits only (algebraic construction extends to any n)
141
+ - **Binary inputs**: Expects {0, 1}, not continuous values
142
+ - **No noise margin**: Heaviside threshold at exactly 0
143
+ - **Not differentiable**: Cannot be fine-tuned with gradient descent
144
+
145
+ ## Files
146
+
147
+ ```
148
+ tiny-mod5-prover/
149
+ ├── model.safetensors # Network weights (146 params)
150
+ ├── model.py # Inference code
151
+ ├── config.json # Model metadata
152
+ └── README.md # This file
153
+ ```
154
+
155
+ ## Citation
156
+
157
+ ```bibtex
158
+ @software{tiny_mod5_prover_2026,
159
+ title={tiny-mod5-prover: Formally Verified Threshold Network for MOD-5},
160
+ author={Norton, Charles},
161
+ url={https://huggingface.co/phanerozoic/tiny-mod5-prover},
162
+ year={2026},
163
+ note={Part of the verified MOD-m threshold circuit family}
164
+ }
165
+ ```
166
+
167
+ ## Related
168
+
169
+ - **Proof repository**: [mod5-verified](https://github.com/CharlesCNorton/mod5-verified)
170
+ - **MOD-3 network**: [tiny-mod3-prover](https://huggingface.co/phanerozoic/tiny-mod3-prover)
171
+ - **Parity network**: [tiny-parity-prover](https://huggingface.co/phanerozoic/tiny-parity-prover)
172
+
173
+ ## License
174
+
175
+ MIT
config.json ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "threshold_network",
3
+ "task": "mod5_classification",
4
+ "architecture": "8 -> 9 -> 4 -> 5",
5
+ "input_size": 8,
6
+ "hidden1_size": 9,
7
+ "hidden2_size": 4,
8
+ "output_size": 5,
9
+ "num_parameters": 146,
10
+ "num_neurons": 18,
11
+ "activation": "heaviside",
12
+ "weight_constraints": "integer",
13
+ "verification": {
14
+ "method": "coq_proof",
15
+ "exhaustive": true,
16
+ "constructive": true,
17
+ "algebraic": true,
18
+ "axiom_free": true
19
+ },
20
+ "accuracy": {
21
+ "all_inputs": "256/256",
22
+ "percentage": 100.0
23
+ },
24
+ "algebraic_insight": "Weights (1,1,1,1,-4) on thermometer encoding produce cumsum = HW mod 5",
25
+ "github": "https://github.com/CharlesCNorton/mod5-verified",
26
+ "related": {
27
+ "mod3_model": "https://huggingface.co/phanerozoic/tiny-mod3-prover",
28
+ "parity_model": "https://huggingface.co/phanerozoic/tiny-parity-prover"
29
+ }
30
+ }
model.py ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Inference code for mod5-verified threshold network.
3
+
4
+ This network computes MOD-5 (Hamming weight mod 5) on 8-bit binary inputs.
5
+ """
6
+
7
+ import torch
8
+ import torch.nn as nn
9
+ from safetensors.torch import load_file
10
+
11
+
12
+ def heaviside(x):
13
+ """Heaviside step function: 1 if x >= 0, else 0."""
14
+ return (x >= 0).float()
15
+
16
+
17
+ class Mod5Network(nn.Module):
18
+ """
19
+ Verified threshold network for MOD-5 computation.
20
+
21
+ Architecture: 8 -> 9 -> 4 -> 5
22
+ - Layer 1: Thermometer encoding (9 neurons detect HW >= k)
23
+ - Layer 2: MOD-5 detection using (1,1,1,1,-4) weight pattern
24
+ - Output: 5-class classification
25
+ """
26
+
27
+ def __init__(self):
28
+ super().__init__()
29
+ self.layer1 = nn.Linear(8, 9)
30
+ self.layer2 = nn.Linear(9, 4)
31
+ self.output = nn.Linear(4, 5)
32
+
33
+ def forward(self, x):
34
+ """Forward pass with Heaviside activation."""
35
+ x = x.float()
36
+ x = heaviside(self.layer1(x))
37
+ x = heaviside(self.layer2(x))
38
+ x = self.output(x)
39
+ return x
40
+
41
+ def predict(self, x):
42
+ """Get predicted class (0, 1, 2, 3, or 4)."""
43
+ return self.forward(x).argmax(dim=-1)
44
+
45
+ @classmethod
46
+ def from_safetensors(cls, path):
47
+ """Load model from safetensors file."""
48
+ model = cls()
49
+ weights = load_file(path)
50
+
51
+ model.layer1.weight.data = weights['layer1.weight']
52
+ model.layer1.bias.data = weights['layer1.bias']
53
+ model.layer2.weight.data = weights['layer2.weight']
54
+ model.layer2.bias.data = weights['layer2.bias']
55
+ model.output.weight.data = weights['output.weight']
56
+ model.output.bias.data = weights['output.bias']
57
+
58
+ return model
59
+
60
+
61
+ def mod5_reference(x):
62
+ """Reference implementation: Hamming weight mod 5."""
63
+ return (x.sum(dim=-1) % 5).long()
64
+
65
+
66
+ def verify(model, verbose=True):
67
+ """Verify model on all 256 inputs."""
68
+ inputs = torch.zeros(256, 8)
69
+ for i in range(256):
70
+ for j in range(8):
71
+ inputs[i, j] = (i >> j) & 1
72
+
73
+ targets = mod5_reference(inputs)
74
+ predictions = model.predict(inputs)
75
+
76
+ correct = (predictions == targets).sum().item()
77
+
78
+ if verbose:
79
+ print(f"Verification: {correct}/256 ({100*correct/256:.1f}%)")
80
+
81
+ if correct < 256:
82
+ errors = (predictions != targets).nonzero(as_tuple=True)[0]
83
+ print(f"Errors at indices: {errors[:10].tolist()}")
84
+
85
+ return correct == 256
86
+
87
+
88
+ def demo():
89
+ """Demonstration of MOD-5 computation."""
90
+ print("Loading mod5-verified model...")
91
+ model = Mod5Network.from_safetensors('model.safetensors')
92
+
93
+ print("\nVerifying on all 256 inputs...")
94
+ verify(model)
95
+
96
+ print("\nExample predictions:")
97
+ test_cases = [
98
+ [0, 0, 0, 0, 0, 0, 0, 0],
99
+ [1, 0, 0, 0, 0, 0, 0, 0],
100
+ [1, 1, 0, 0, 0, 0, 0, 0],
101
+ [1, 1, 1, 0, 0, 0, 0, 0],
102
+ [1, 1, 1, 1, 0, 0, 0, 0],
103
+ [1, 1, 1, 1, 1, 0, 0, 0],
104
+ [1, 1, 1, 1, 1, 1, 0, 0],
105
+ [1, 1, 1, 1, 1, 1, 1, 0],
106
+ [1, 1, 1, 1, 1, 1, 1, 1],
107
+ ]
108
+
109
+ for bits in test_cases:
110
+ x = torch.tensor([bits], dtype=torch.float32)
111
+ hw = sum(bits)
112
+ pred = model.predict(x).item()
113
+ expected = hw % 5
114
+ status = "OK" if pred == expected else "ERROR"
115
+ print(f" {bits} -> HW={hw}, pred={pred}, expected={expected} [{status}]")
116
+
117
+
118
+ if __name__ == '__main__':
119
+ demo()
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:56840ae01951fd606bfc6aadb521629c1860752d8886efaad1ecd6bd2583707d
3
+ size 1008