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

4-bit population count, magnitude 55

Browse files
Files changed (5) hide show
  1. README.md +86 -0
  2. config.json +9 -0
  3. create_safetensors.py +114 -0
  4. model.py +43 -0
  5. model.safetensors +0 -0
README.md ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ tags:
4
+ - pytorch
5
+ - safetensors
6
+ - threshold-logic
7
+ - neuromorphic
8
+ ---
9
+
10
+ # threshold-popcount4
11
+
12
+ 4-bit population count. Counts the number of 1 bits in a 4-bit input.
13
+
14
+ ## Function
15
+
16
+ popcount4(a, b, c, d) = binary count of 1 bits
17
+
18
+ Output [y2, y1, y0] is the 3-bit binary representation of the count (0-4).
19
+
20
+ ## Truth Table
21
+
22
+ | a | b | c | d | count | y2 | y1 | y0 |
23
+ |---|---|---|---|-------|----|----|-----|
24
+ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
25
+ | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 1 |
26
+ | 0 | 0 | 1 | 1 | 2 | 0 | 1 | 0 |
27
+ | 0 | 1 | 1 | 1 | 3 | 0 | 1 | 1 |
28
+ | 1 | 1 | 1 | 1 | 4 | 1 | 0 | 0 |
29
+
30
+ ## Architecture
31
+
32
+ ```
33
+ a b c d
34
+ | | | |
35
+ +----+---+---+---+----+
36
+ | |
37
+ Layer 1: |
38
+ y2 = (sum >= 4) |
39
+ ge2 = (sum >= 2) |
40
+ le3 = (sum <= 3) |
41
+ XOR(a,b) components |
42
+ XOR(c,d) components |
43
+ | |
44
+ Layer 2: |
45
+ y1 = AND(ge2, le3) |
46
+ xor_ab = XOR(a,b) |
47
+ xor_cd = XOR(c,d) |
48
+ | |
49
+ Layer 3: |
50
+ XOR(xor_ab, xor_cd) |
51
+ components |
52
+ | |
53
+ Layer 4: |
54
+ y0 = XOR(xor_ab, xor_cd)
55
+ ```
56
+
57
+ - **y2** (MSB): 1 when count = 4
58
+ - **y1**: 1 when count is 2 or 3
59
+ - **y0** (LSB): 1 when count is odd (parity = XOR4)
60
+
61
+ ## Parameters
62
+
63
+ | | |
64
+ |---|---|
65
+ | Inputs | 4 |
66
+ | Outputs | 3 |
67
+ | Neurons | 13 |
68
+ | Layers | 4 |
69
+ | Parameters | 53 |
70
+ | Magnitude | 55 |
71
+
72
+ ## Usage
73
+
74
+ ```python
75
+ from safetensors.torch import load_file
76
+ import torch
77
+
78
+ w = load_file('model.safetensors')
79
+
80
+ # See model.py for full implementation
81
+ # popcount4(1, 0, 1, 1) returns [0, 1, 1] (count=3 = binary 011)
82
+ ```
83
+
84
+ ## License
85
+
86
+ MIT
config.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "threshold-popcount4",
3
+ "description": "4-bit population count (count 1 bits)",
4
+ "inputs": 4,
5
+ "outputs": 3,
6
+ "neurons": 13,
7
+ "layers": 4,
8
+ "parameters": 53
9
+ }
create_safetensors.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from safetensors.torch import save_file
3
+
4
+ weights = {}
5
+
6
+ # Architecture:
7
+ # y2 = (sum >= 4) - 1 layer
8
+ # y1 = (sum >= 2) AND (sum <= 3) - 2 layers
9
+ # y0 = XOR4(a,b,c,d) = XOR(XOR(a,b), XOR(c,d)) - 4 layers
10
+
11
+ # Layer 1 (inputs: a,b,c,d)
12
+ # y2: sum >= 4
13
+ weights['y2.weight'] = torch.tensor([[1.0, 1.0, 1.0, 1.0]], dtype=torch.float32)
14
+ weights['y2.bias'] = torch.tensor([-4.0], dtype=torch.float32)
15
+
16
+ # ge2: sum >= 2
17
+ weights['ge2.weight'] = torch.tensor([[1.0, 1.0, 1.0, 1.0]], dtype=torch.float32)
18
+ weights['ge2.bias'] = torch.tensor([-2.0], dtype=torch.float32)
19
+
20
+ # le3: sum <= 3
21
+ weights['le3.weight'] = torch.tensor([[-1.0, -1.0, -1.0, -1.0]], dtype=torch.float32)
22
+ weights['le3.bias'] = torch.tensor([3.0], dtype=torch.float32)
23
+
24
+ # XOR(a,b) components
25
+ weights['xor_ab_or.weight'] = torch.tensor([[1.0, 1.0, 0.0, 0.0]], dtype=torch.float32)
26
+ weights['xor_ab_or.bias'] = torch.tensor([-1.0], dtype=torch.float32)
27
+ weights['xor_ab_nand.weight'] = torch.tensor([[-1.0, -1.0, 0.0, 0.0]], dtype=torch.float32)
28
+ weights['xor_ab_nand.bias'] = torch.tensor([1.0], dtype=torch.float32)
29
+
30
+ # XOR(c,d) components
31
+ weights['xor_cd_or.weight'] = torch.tensor([[0.0, 0.0, 1.0, 1.0]], dtype=torch.float32)
32
+ weights['xor_cd_or.bias'] = torch.tensor([-1.0], dtype=torch.float32)
33
+ weights['xor_cd_nand.weight'] = torch.tensor([[0.0, 0.0, -1.0, -1.0]], dtype=torch.float32)
34
+ weights['xor_cd_nand.bias'] = torch.tensor([1.0], dtype=torch.float32)
35
+
36
+ # Layer 2
37
+ # y1 = AND(ge2, le3)
38
+ weights['y1.weight'] = torch.tensor([[1.0, 1.0]], dtype=torch.float32) # [ge2, le3]
39
+ weights['y1.bias'] = torch.tensor([-2.0], dtype=torch.float32)
40
+
41
+ # xor_ab = AND(xor_ab_or, xor_ab_nand)
42
+ weights['xor_ab.weight'] = torch.tensor([[1.0, 1.0]], dtype=torch.float32)
43
+ weights['xor_ab.bias'] = torch.tensor([-2.0], dtype=torch.float32)
44
+
45
+ # xor_cd = AND(xor_cd_or, xor_cd_nand)
46
+ weights['xor_cd.weight'] = torch.tensor([[1.0, 1.0]], dtype=torch.float32)
47
+ weights['xor_cd.bias'] = torch.tensor([-2.0], dtype=torch.float32)
48
+
49
+ # Layer 3: XOR(xor_ab, xor_cd) components
50
+ weights['xor_final_or.weight'] = torch.tensor([[1.0, 1.0]], dtype=torch.float32)
51
+ weights['xor_final_or.bias'] = torch.tensor([-1.0], dtype=torch.float32)
52
+ weights['xor_final_nand.weight'] = torch.tensor([[-1.0, -1.0]], dtype=torch.float32)
53
+ weights['xor_final_nand.bias'] = torch.tensor([1.0], dtype=torch.float32)
54
+
55
+ # Layer 4: y0 = AND(xor_final_or, xor_final_nand)
56
+ weights['y0.weight'] = torch.tensor([[1.0, 1.0]], dtype=torch.float32)
57
+ weights['y0.bias'] = torch.tensor([-2.0], dtype=torch.float32)
58
+
59
+ save_file(weights, 'model.safetensors')
60
+
61
+ # Verify
62
+ def popcount4(a, b, c, d):
63
+ inp = torch.tensor([float(a), float(b), float(c), float(d)])
64
+
65
+ # Layer 1
66
+ y2 = int((inp @ weights['y2.weight'].T + weights['y2.bias'] >= 0).item())
67
+ ge2 = int((inp @ weights['ge2.weight'].T + weights['ge2.bias'] >= 0).item())
68
+ le3 = int((inp @ weights['le3.weight'].T + weights['le3.bias'] >= 0).item())
69
+ xor_ab_or = int((inp @ weights['xor_ab_or.weight'].T + weights['xor_ab_or.bias'] >= 0).item())
70
+ xor_ab_nand = int((inp @ weights['xor_ab_nand.weight'].T + weights['xor_ab_nand.bias'] >= 0).item())
71
+ xor_cd_or = int((inp @ weights['xor_cd_or.weight'].T + weights['xor_cd_or.bias'] >= 0).item())
72
+ xor_cd_nand = int((inp @ weights['xor_cd_nand.weight'].T + weights['xor_cd_nand.bias'] >= 0).item())
73
+
74
+ # Layer 2
75
+ l2_y1_in = torch.tensor([float(ge2), float(le3)])
76
+ y1 = int((l2_y1_in @ weights['y1.weight'].T + weights['y1.bias'] >= 0).item())
77
+
78
+ l2_xor_ab_in = torch.tensor([float(xor_ab_or), float(xor_ab_nand)])
79
+ xor_ab = int((l2_xor_ab_in @ weights['xor_ab.weight'].T + weights['xor_ab.bias'] >= 0).item())
80
+
81
+ l2_xor_cd_in = torch.tensor([float(xor_cd_or), float(xor_cd_nand)])
82
+ xor_cd = int((l2_xor_cd_in @ weights['xor_cd.weight'].T + weights['xor_cd.bias'] >= 0).item())
83
+
84
+ # Layer 3
85
+ l3_in = torch.tensor([float(xor_ab), float(xor_cd)])
86
+ xor_final_or = int((l3_in @ weights['xor_final_or.weight'].T + weights['xor_final_or.bias'] >= 0).item())
87
+ xor_final_nand = int((l3_in @ weights['xor_final_nand.weight'].T + weights['xor_final_nand.bias'] >= 0).item())
88
+
89
+ # Layer 4
90
+ l4_in = torch.tensor([float(xor_final_or), float(xor_final_nand)])
91
+ y0 = int((l4_in @ weights['y0.weight'].T + weights['y0.bias'] >= 0).item())
92
+
93
+ return [y2, y1, y0]
94
+
95
+ print("Verifying popcount4...")
96
+ errors = 0
97
+ for i in range(16):
98
+ a, b, c, d = (i >> 3) & 1, (i >> 2) & 1, (i >> 1) & 1, i & 1
99
+ result = popcount4(a, b, c, d)
100
+ count = a + b + c + d
101
+ expected = [(count >> 2) & 1, (count >> 1) & 1, count & 1]
102
+ if result != expected:
103
+ errors += 1
104
+ print(f"ERROR: {a}{b}{c}{d} count={count} -> {result}, expected {expected}")
105
+
106
+ if errors == 0:
107
+ print("All 16 test cases passed!")
108
+ else:
109
+ print(f"FAILED: {errors} errors")
110
+
111
+ mag = sum(t.abs().sum().item() for t in weights.values())
112
+ print(f"Magnitude: {mag:.0f}")
113
+ print(f"Neurons: 13")
114
+ print(f"Parameters: {sum(t.numel() for t in weights.values())}")
model.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ def popcount4(a, b, c, d, w):
8
+ """Count number of 1 bits in 4-bit input. Returns [y2, y1, y0] = binary count."""
9
+ inp = torch.tensor([float(a), float(b), float(c), float(d)])
10
+
11
+ # Layer 1
12
+ y2 = int((inp @ w['y2.weight'].T + w['y2.bias'] >= 0).item())
13
+ ge2 = int((inp @ w['ge2.weight'].T + w['ge2.bias'] >= 0).item())
14
+ le3 = int((inp @ w['le3.weight'].T + w['le3.bias'] >= 0).item())
15
+ xor_ab_or = int((inp @ w['xor_ab_or.weight'].T + w['xor_ab_or.bias'] >= 0).item())
16
+ xor_ab_nand = int((inp @ w['xor_ab_nand.weight'].T + w['xor_ab_nand.bias'] >= 0).item())
17
+ xor_cd_or = int((inp @ w['xor_cd_or.weight'].T + w['xor_cd_or.bias'] >= 0).item())
18
+ xor_cd_nand = int((inp @ w['xor_cd_nand.weight'].T + w['xor_cd_nand.bias'] >= 0).item())
19
+
20
+ # Layer 2
21
+ y1 = int(ge2 + le3 - 2 >= 0)
22
+ xor_ab = int(xor_ab_or + xor_ab_nand - 2 >= 0)
23
+ xor_cd = int(xor_cd_or + xor_cd_nand - 2 >= 0)
24
+
25
+ # Layer 3
26
+ xor_final_or = int(xor_ab + xor_cd - 1 >= 0)
27
+ xor_final_nand = int(-xor_ab - xor_cd + 1 >= 0)
28
+
29
+ # Layer 4
30
+ y0 = int(xor_final_or + xor_final_nand - 2 >= 0)
31
+
32
+ return [y2, y1, y0]
33
+
34
+ if __name__ == '__main__':
35
+ w = load_model()
36
+ print('popcount4 truth table:')
37
+ print('abcd | count | y2 y1 y0')
38
+ print('-----+-------+---------')
39
+ for i in range(16):
40
+ a, b, c, d = (i >> 3) & 1, (i >> 2) & 1, (i >> 1) & 1, i & 1
41
+ result = popcount4(a, b, c, d, w)
42
+ count = a + b + c + d
43
+ print(f'{a}{b}{c}{d} | {count} | {result[0]} {result[1]} {result[2]}')
model.safetensors ADDED
Binary file (2.02 kB). View file