CharlesCNorton commited on
Commit
ff90a24
·
0 Parent(s):

4-bit absolute value threshold circuit, magnitude 64

Browse files
Files changed (5) hide show
  1. README.md +64 -0
  2. config.json +9 -0
  3. create_safetensors.py +196 -0
  4. model.py +14 -0
  5. model.safetensors +0 -0
README.md ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ tags:
4
+ - pytorch
5
+ - safetensors
6
+ - threshold-logic
7
+ - neuromorphic
8
+ ---
9
+
10
+ # threshold-absolutevalue4
11
+
12
+ Absolute value of 4-bit 2's complement signed integer.
13
+
14
+ ## Function
15
+
16
+ abs4(a) = |a| where a is interpreted as 4-bit 2's complement (-8 to +7)
17
+
18
+ ## Truth Table
19
+
20
+ | Input | Signed | |Abs| | Output |
21
+ |-------|--------|------|--------|
22
+ | 0000 | +0 | 0 | 0000 |
23
+ | 0001 | +1 | 1 | 0001 |
24
+ | 0010 | +2 | 2 | 0010 |
25
+ | 0011 | +3 | 3 | 0011 |
26
+ | 0100 | +4 | 4 | 0100 |
27
+ | 0101 | +5 | 5 | 0101 |
28
+ | 0110 | +6 | 6 | 0110 |
29
+ | 0111 | +7 | 7 | 0111 |
30
+ | 1000 | -8 | 8 | 1000 |
31
+ | 1001 | -7 | 7 | 0111 |
32
+ | 1010 | -6 | 6 | 0110 |
33
+ | 1011 | -5 | 5 | 0101 |
34
+ | 1100 | -4 | 4 | 0100 |
35
+ | 1101 | -3 | 3 | 0011 |
36
+ | 1110 | -2 | 2 | 0010 |
37
+ | 1111 | -1 | 1 | 0001 |
38
+
39
+ ## Architecture
40
+
41
+ 5-layer circuit implementing conditional 2's complement negation:
42
+ - For positive (a3=0): output = input
43
+ - For negative (a3=1): output = ~input + 1
44
+
45
+ Key formulas for negative path:
46
+ - o0 = a0 (always)
47
+ - o1 = ~a1 XOR ~a0
48
+ - o2 = ~a2 XOR (~a1 AND ~a0)
49
+ - o3 = 1 only for input 1000 (-8)
50
+
51
+ ## Parameters
52
+
53
+ | | |
54
+ |---|---|
55
+ | Inputs | 4 |
56
+ | Outputs | 4 |
57
+ | Neurons | 23 |
58
+ | Layers | 5 |
59
+ | Parameters | 145 |
60
+ | Magnitude | 64 |
61
+
62
+ ## License
63
+
64
+ MIT
config.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "threshold-absolutevalue4",
3
+ "description": "4-bit absolute value (2's complement)",
4
+ "inputs": 4,
5
+ "outputs": 4,
6
+ "neurons": 23,
7
+ "layers": 5,
8
+ "parameters": 145
9
+ }
create_safetensors.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from safetensors.torch import save_file
3
+
4
+ # Absolute value of 4-bit 2's complement number
5
+ # Input: a3 (sign), a2, a1, a0
6
+ # Output: o3, o2, o1, o0
7
+ #
8
+ # For positive (a3=0): output = input
9
+ # For negative (a3=1): output = ~input + 1 (2's complement)
10
+ #
11
+ # 2's complement bit formulas:
12
+ # o0 = a0 (always)
13
+ # o1 = ~a1 XOR ~a0 (when negative)
14
+ # o2 = ~a2 XOR (~a1 AND ~a0) (when negative)
15
+ # o3 = ~a3 XOR (~a2 AND ~a1 AND ~a0) = carry (when negative, and ~a3=0 for a3=1)
16
+
17
+ weights = {}
18
+
19
+ # === DIRECT OUTPUTS ===
20
+ # o0 = a0 (always)
21
+ weights['o0.weight'] = torch.tensor([[0.0, 0.0, 0.0, 1.0]], dtype=torch.float32)
22
+ weights['o0.bias'] = torch.tensor([-0.5], dtype=torch.float32)
23
+
24
+ # o3 = 1 only when input is 1000 (-8), i.e., when carry propagates all the way
25
+ # o3 = a3 AND NOT(a2) AND NOT(a1) AND NOT(a0)
26
+ weights['o3.weight'] = torch.tensor([[1.0, -1.0, -1.0, -1.0]], dtype=torch.float32)
27
+ weights['o3.bias'] = torch.tensor([-1.0], dtype=torch.float32)
28
+
29
+ # === LAYER 1 (from raw inputs a3, a2, a1, a0) ===
30
+
31
+ # NOT gates
32
+ weights['l1.not_a0.weight'] = torch.tensor([[0.0, 0.0, 0.0, -1.0]], dtype=torch.float32)
33
+ weights['l1.not_a0.bias'] = torch.tensor([0.0], dtype=torch.float32)
34
+
35
+ weights['l1.not_a1.weight'] = torch.tensor([[0.0, 0.0, -1.0, 0.0]], dtype=torch.float32)
36
+ weights['l1.not_a1.bias'] = torch.tensor([0.0], dtype=torch.float32)
37
+
38
+ weights['l1.not_a2.weight'] = torch.tensor([[0.0, -1.0, 0.0, 0.0]], dtype=torch.float32)
39
+ weights['l1.not_a2.bias'] = torch.tensor([0.0], dtype=torch.float32)
40
+
41
+ # XOR(~a1, ~a0) = o1 for negative path
42
+ # Components: OR(~a1, ~a0), NAND(~a1, ~a0)
43
+ # OR(~a1, ~a0) = NOR(a1, a0) inverted... actually OR(~a1,~a0) = NAND(a1,a0)
44
+ weights['l1.nand10.weight'] = torch.tensor([[0.0, 0.0, -1.0, -1.0]], dtype=torch.float32)
45
+ weights['l1.nand10.bias'] = torch.tensor([1.0], dtype=torch.float32)
46
+
47
+ # NAND(~a1, ~a0) = NOT(~a1 AND ~a0) = NOT(NOR(a1,a0)) = OR(a1,a0)
48
+ weights['l1.or10.weight'] = torch.tensor([[0.0, 0.0, 1.0, 1.0]], dtype=torch.float32)
49
+ weights['l1.or10.bias'] = torch.tensor([-1.0], dtype=torch.float32)
50
+
51
+ # NOR(a1, a0) = ~a1 AND ~a0 = carry for bit 2
52
+ weights['l1.nor10.weight'] = torch.tensor([[0.0, 0.0, -1.0, -1.0]], dtype=torch.float32)
53
+ weights['l1.nor10.bias'] = torch.tensor([0.0], dtype=torch.float32)
54
+
55
+ # Positive path: pass through a1 when a3=0
56
+ weights['l1.o1_pos.weight'] = torch.tensor([[-1.0, 0.0, 1.0, 0.0]], dtype=torch.float32)
57
+ weights['l1.o1_pos.bias'] = torch.tensor([-1.0], dtype=torch.float32)
58
+
59
+ # Positive path: pass through a2 when a3=0
60
+ weights['l1.o2_pos.weight'] = torch.tensor([[-1.0, 1.0, 0.0, 0.0]], dtype=torch.float32)
61
+ weights['l1.o2_pos.bias'] = torch.tensor([-1.0], dtype=torch.float32)
62
+
63
+ # a3 passthrough
64
+ weights['l1.a3.weight'] = torch.tensor([[1.0, 0.0, 0.0, 0.0]], dtype=torch.float32)
65
+ weights['l1.a3.bias'] = torch.tensor([-0.5], dtype=torch.float32)
66
+
67
+ # === LAYER 2 ===
68
+ # Inputs: [not_a0, not_a1, not_a2, nand10, or10, nor10, o1_pos, o2_pos, a3]
69
+
70
+ # XOR(~a1, ~a0) = nand10 AND or10 (since nand10 = OR(~a1,~a0), or10 = NAND(~a1,~a0))
71
+ # Wait, let me reconsider:
72
+ # XOR(~a1, ~a0) = (~a1 OR ~a0) AND NOT(~a1 AND ~a0)
73
+ # = NAND(a1,a0) AND OR(a1,a0)
74
+ # nand10 = NAND(a1,a0) ✓
75
+ # or10 = OR(a1,a0) ✓
76
+ weights['l2.xor_neg_10.weight'] = torch.tensor([[0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0]], dtype=torch.float32)
77
+ weights['l2.xor_neg_10.bias'] = torch.tensor([-2.0], dtype=torch.float32)
78
+
79
+ # For XOR(~a2, nor10): need OR(~a2, nor10) AND NAND(~a2, nor10)
80
+ # We have not_a2 and nor10 from layer 1
81
+ weights['l2.or_nota2_nor10.weight'] = torch.tensor([[0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0]], dtype=torch.float32)
82
+ weights['l2.or_nota2_nor10.bias'] = torch.tensor([-1.0], dtype=torch.float32)
83
+
84
+ weights['l2.nand_nota2_nor10.weight'] = torch.tensor([[0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0]], dtype=torch.float32)
85
+ weights['l2.nand_nota2_nor10.bias'] = torch.tensor([1.0], dtype=torch.float32)
86
+
87
+ # Pass through o1_pos, o2_pos, a3
88
+ weights['l2.o1_pos.weight'] = torch.tensor([[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]], dtype=torch.float32)
89
+ weights['l2.o1_pos.bias'] = torch.tensor([-0.5], dtype=torch.float32)
90
+
91
+ weights['l2.o2_pos.weight'] = torch.tensor([[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0]], dtype=torch.float32)
92
+ weights['l2.o2_pos.bias'] = torch.tensor([-0.5], dtype=torch.float32)
93
+
94
+ weights['l2.a3.weight'] = torch.tensor([[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]], dtype=torch.float32)
95
+ weights['l2.a3.bias'] = torch.tensor([-0.5], dtype=torch.float32)
96
+
97
+ # === LAYER 3 ===
98
+ # Inputs: [xor_neg_10, or_nota2_nor10, nand_nota2_nor10, o1_pos, o2_pos, a3]
99
+
100
+ # XOR(~a2, nor10) = or_nota2_nor10 AND nand_nota2_nor10
101
+ weights['l3.xor_neg_2.weight'] = torch.tensor([[0.0, 1.0, 1.0, 0.0, 0.0, 0.0]], dtype=torch.float32)
102
+ weights['l3.xor_neg_2.bias'] = torch.tensor([-2.0], dtype=torch.float32)
103
+
104
+ # o1_neg = xor_neg_10 AND a3
105
+ weights['l3.o1_neg.weight'] = torch.tensor([[1.0, 0.0, 0.0, 0.0, 0.0, 1.0]], dtype=torch.float32)
106
+ weights['l3.o1_neg.bias'] = torch.tensor([-2.0], dtype=torch.float32)
107
+
108
+ # Pass through o1_pos, o2_pos, a3
109
+ weights['l3.o1_pos.weight'] = torch.tensor([[0.0, 0.0, 0.0, 1.0, 0.0, 0.0]], dtype=torch.float32)
110
+ weights['l3.o1_pos.bias'] = torch.tensor([-0.5], dtype=torch.float32)
111
+
112
+ weights['l3.o2_pos.weight'] = torch.tensor([[0.0, 0.0, 0.0, 0.0, 1.0, 0.0]], dtype=torch.float32)
113
+ weights['l3.o2_pos.bias'] = torch.tensor([-0.5], dtype=torch.float32)
114
+
115
+ weights['l3.a3.weight'] = torch.tensor([[0.0, 0.0, 0.0, 0.0, 0.0, 1.0]], dtype=torch.float32)
116
+ weights['l3.a3.bias'] = torch.tensor([-0.5], dtype=torch.float32)
117
+
118
+ # === LAYER 4 ===
119
+ # Inputs: [xor_neg_2, o1_neg, o1_pos, o2_pos, a3]
120
+
121
+ # o2_neg = xor_neg_2 AND a3
122
+ weights['l4.o2_neg.weight'] = torch.tensor([[1.0, 0.0, 0.0, 0.0, 1.0]], dtype=torch.float32)
123
+ weights['l4.o2_neg.bias'] = torch.tensor([-2.0], dtype=torch.float32)
124
+
125
+ # o1 = o1_pos OR o1_neg
126
+ weights['l4.o1.weight'] = torch.tensor([[0.0, 1.0, 1.0, 0.0, 0.0]], dtype=torch.float32)
127
+ weights['l4.o1.bias'] = torch.tensor([-1.0], dtype=torch.float32)
128
+
129
+ # Pass through o2_pos
130
+ weights['l4.o2_pos.weight'] = torch.tensor([[0.0, 0.0, 0.0, 1.0, 0.0]], dtype=torch.float32)
131
+ weights['l4.o2_pos.bias'] = torch.tensor([-0.5], dtype=torch.float32)
132
+
133
+ # === LAYER 5 ===
134
+ # Inputs: [o2_neg, o2_pos]
135
+ # o2 = o2_pos OR o2_neg
136
+ weights['l5.o2.weight'] = torch.tensor([[1.0, 1.0]], dtype=torch.float32)
137
+ weights['l5.o2.bias'] = torch.tensor([-1.0], dtype=torch.float32)
138
+
139
+ save_file(weights, 'model.safetensors')
140
+
141
+ # Verification
142
+ def abs4(a3, a2, a1, a0):
143
+ inp = torch.tensor([float(a3), float(a2), float(a1), float(a0)])
144
+
145
+ # Direct outputs
146
+ o0 = int((inp @ weights['o0.weight'].T + weights['o0.bias'] >= 0).item())
147
+ o3 = int((inp @ weights['o3.weight'].T + weights['o3.bias'] >= 0).item())
148
+
149
+ # Layer 1
150
+ l1_keys = ['not_a0', 'not_a1', 'not_a2', 'nand10', 'or10', 'nor10', 'o1_pos', 'o2_pos', 'a3']
151
+ l1 = {k: int((inp @ weights[f'l1.{k}.weight'].T + weights[f'l1.{k}.bias'] >= 0).item()) for k in l1_keys}
152
+ l1_out = torch.tensor([float(l1[k]) for k in l1_keys])
153
+
154
+ # Layer 2
155
+ l2_keys = ['xor_neg_10', 'or_nota2_nor10', 'nand_nota2_nor10', 'o1_pos', 'o2_pos', 'a3']
156
+ l2 = {k: int((l1_out @ weights[f'l2.{k}.weight'].T + weights[f'l2.{k}.bias'] >= 0).item()) for k in l2_keys}
157
+ l2_out = torch.tensor([float(l2[k]) for k in l2_keys])
158
+
159
+ # Layer 3
160
+ l3_keys = ['xor_neg_2', 'o1_neg', 'o1_pos', 'o2_pos', 'a3']
161
+ l3 = {k: int((l2_out @ weights[f'l3.{k}.weight'].T + weights[f'l3.{k}.bias'] >= 0).item()) for k in l3_keys}
162
+ l3_out = torch.tensor([float(l3[k]) for k in l3_keys])
163
+
164
+ # Layer 4
165
+ l4_keys = ['o2_neg', 'o1', 'o2_pos']
166
+ l4 = {k: int((l3_out @ weights[f'l4.{k}.weight'].T + weights[f'l4.{k}.bias'] >= 0).item()) for k in l4_keys}
167
+
168
+ o1 = l4['o1']
169
+
170
+ # Layer 5
171
+ l5_inp = torch.tensor([float(l4['o2_neg']), float(l4['o2_pos'])])
172
+ o2 = int((l5_inp @ weights['l5.o2.weight'].T + weights['l5.o2.bias'] >= 0).item())
173
+
174
+ return o3, o2, o1, o0
175
+
176
+ print("Verifying absolutevalue4...")
177
+ errors = 0
178
+ for a in range(16):
179
+ a3, a2, a1, a0 = (a >> 3) & 1, (a >> 2) & 1, (a >> 1) & 1, a & 1
180
+ o3, o2, o1, o0 = abs4(a3, a2, a1, a0)
181
+ result = 8*o3 + 4*o2 + 2*o1 + o0
182
+
183
+ signed_val = a if a < 8 else a - 16
184
+ expected = abs(signed_val)
185
+
186
+ if result != expected:
187
+ errors += 1
188
+ print(f"ERROR: input={a:04b} ({signed_val:+d}), got {result}, expected {expected}")
189
+
190
+ if errors == 0:
191
+ print("All 16 test cases passed!")
192
+ else:
193
+ print(f"FAILED: {errors} errors")
194
+
195
+ mag = sum(t.abs().sum().item() for t in weights.values())
196
+ print(f"Magnitude: {mag:.0f}")
model.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from safetensors.torch import load_file
3
+
4
+ def load_model(path='model.safetensors'):
5
+ return load_file(path)
6
+
7
+ if __name__ == '__main__':
8
+ w = load_model()
9
+ print('Absolute Value 4-bit (2\'s complement):')
10
+ print('Input Signed |Abs| Output')
11
+ for a in range(16):
12
+ signed = a if a < 8 else a - 16
13
+ expected = abs(signed)
14
+ print(f' {a:04b} {signed:+2d} {expected} {expected:04b}')
model.safetensors ADDED
Binary file (4.4 kB). View file