CharlesCNorton commited on
Commit
4bb9078
·
1 Parent(s): 9d8fd84

Add neural_reflect: a threshold interpreter whose state holds its own weights

Browse files
README.md CHANGED
@@ -37,10 +37,11 @@ variants/neural_subleq8.safetensors one-instruction
37
  variants/neural_rv32.safetensors RV32IM + F-subset RISC-V processor
38
  variants/neural_matrix8.safetensors the CPU as one recurrent ternary matrix stack
39
  variants/neural_subleq8io.safetensors SUBLEQ host for the universal constructor
 
40
  ```
41
 
42
- Four further machines are detailed in their own sections below, and together
43
- they carry the family from the smallest possible processor to two results
44
  about what a threshold network can be. `neural_subleq8` is a Turing-complete
45
  one-instruction computer whose entire control flow is a single threshold
46
  neuron. `neural_rv32` is a RISC-V processor (RV32IM plus an F subset) that
@@ -49,10 +50,13 @@ self-referential NEUR opcode. `neural_matrix8` drops the gate graph entirely:
49
  the whole processor is compiled into a fixed stack of ternary weight matrices
50
  with a Heaviside step between them, so one clock cycle is one matrix-vector
51
  product followed by thresholding, exactly what a resistive or photonic
52
- crossbar computes. And `neural_subleq8io` hosts a universal constructor: a
53
  program that reads a description of any machine in the family and prints that
54
  machine's weight file byte for byte, self-reproduction being the case where
55
- the description is its own.
 
 
 
56
 
57
  ---
58
 
@@ -498,6 +502,54 @@ weights and checked to the byte.
498
 
499
  ---
500
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
  ## Threshold logic
502
 
503
  A threshold gate computes a Boolean function by taking a weighted sum of binary inputs and comparing the result to a threshold; the output is 1 when the sum meets or exceeds the threshold and 0 otherwise. Equivalently, it is a neuron with Heaviside step activation, integer weights, and an integer bias.
@@ -670,8 +722,9 @@ Loss components: BCE on output bits, BCE on extracted A and B bits (2× weight),
670
 
671
  ```
672
  neural_computer.safetensors canonical model (32-bit, 64 KB, ~8.61M params)
673
- variants/ 18 fitness variants + 4 standalone machines
674
- (neural_subleq8, neural_rv32, neural_matrix8, neural_subleq8io)
 
675
  src/ the library (run scripts as `python src/<name>.py`)
676
  ├── build.py generator (one safetensors per invocation; also `subleq`, `rv32`)
677
  ├── quantize.py min integer dtypes + ternary verification/repair
@@ -681,8 +734,10 @@ src/ the library (run scripts as `python src/<nam
681
  │ RV32 object loader, and the test suites (subleq / rv32 / rv32-c)
682
  ├── matrix8.py compiles the CPU to a recurrent ternary matrix stack; the
683
  │ exhaustive equality suite and the analog crossbar simulation
684
- ── constructor8.py the neural_subleq8io host, the recipe codec, and the universal
685
- constructor / self-reproduction suite
 
 
686
  tools/ build_all.py (build + quantize + verify every profile),
687
  cpu_programs.py (assembler + CPU program suite), test_cpu.py
688
  (program suite vs a variant), play.py (interactive demo),
 
37
  variants/neural_rv32.safetensors RV32IM + F-subset RISC-V processor
38
  variants/neural_matrix8.safetensors the CPU as one recurrent ternary matrix stack
39
  variants/neural_subleq8io.safetensors SUBLEQ host for the universal constructor
40
+ variants/neural_reflect.safetensors interpreter whose state holds its own weights
41
  ```
42
 
43
+ Five further machines are detailed in their own sections below, and together
44
+ they carry the family from the smallest possible processor to three results
45
  about what a threshold network can be. `neural_subleq8` is a Turing-complete
46
  one-instruction computer whose entire control flow is a single threshold
47
  neuron. `neural_rv32` is a RISC-V processor (RV32IM plus an F subset) that
 
50
  the whole processor is compiled into a fixed stack of ternary weight matrices
51
  with a Heaviside step between them, so one clock cycle is one matrix-vector
52
  product followed by thresholding, exactly what a resistive or photonic
53
+ crossbar computes. `neural_subleq8io` hosts a universal constructor: a
54
  program that reads a description of any machine in the family and prints that
55
  machine's weight file byte for byte, self-reproduction being the case where
56
+ the description is its own. And `neural_reflect` removes the last fixed part:
57
+ its transition is a ternary threshold network whose weights are stored in the
58
+ writable state it operates on, so a running program can edit the weights that
59
+ define it.
60
 
61
  ---
62
 
 
502
 
503
  ---
504
 
505
+ ## neural_reflect — an interpreter whose state holds its own weights
506
+
507
+ Every machine above has a fixed weight tensor: the weights define the machine,
508
+ the state is what it operates on, and the two are disjoint. `neural_reflect`
509
+ removes that separation. The state is a flat array of 256 binary signals; a
510
+ 192-bit block of them (the NET region) encodes a netlist of six threshold
511
+ gates, and a fixed ternary interpreter U evaluates one stored gate per
512
+ recurrence. It selects gate `gp`, reads that gate's two ternary input weights,
513
+ integer bias, two input addresses, and one output address from the NET bits,
514
+ fetches the two addressed signals, computes `out = H(w0·v0 + w1·v1 + b)`,
515
+ writes `out` to the addressed signal, and advances `gp`. Six recurrences
516
+ evaluate the stored netlist once.
517
+
518
+ - **The program shares memory with the data.** Addresses index the whole
519
+ signal array, so a gate's output can target the NET region: a stored gate
520
+ can overwrite another gate's weight, bias, or wiring. The running program
521
+ edits the weights that define it (self-modification), and rewriting the NET
522
+ region replaces the interpreted machine with another one, with no external
523
+ load step (metamorphosis).
524
+ - **U is fixed and ternary.** The interpreter is one threshold circuit built
525
+ from the family's cells (address decoders, 2:1 muxes, a ternary
526
+ multiply-accumulate over full adders, Heaviside), 2,938 gates compiled to 27
527
+ ternary matrices by `matrix8`'s compiler. Its behavior is set entirely by
528
+ the netlist bits in the state, not by its own weights.
529
+ - **Physical reading.** One recurrence is one matrix-vector product with a
530
+ threshold, and the write is a single bit flip in the state. On a memristive
531
+ crossbar that bit is a conductance, so a program editing the NET region is
532
+ the computation editing the array's own weights as it runs.
533
+
534
+ Verified against a reference interpreter: exhaustive single-gate semantics
535
+ (every ternary weight pair, bias, and input assignment), single-step agreement
536
+ on 3,000 random full states plus halted fixed points, lockstep interpretation
537
+ of a composed XOR built from the family's gates, and a program that rewrites
538
+ its own AND gate into an OR gate between sweeps (output `a & b` on the first
539
+ sweep, `a | b` on the second). The compiled matrix form is checked equal to
540
+ the gate graph bit for bit.
541
+
542
+ ```bash
543
+ python src/reflect.py build # compile + save variants/neural_reflect.safetensors
544
+ python src/reflect.py verify # reference equality, self-modification, matrix == gate
545
+ ```
546
+
547
+ The constructor prints a machine's weights; this interprets them out of its
548
+ own memory and can rewrite them mid-run. The transition and the state it
549
+ transforms are the same tensor.
550
+
551
+ ---
552
+
553
  ## Threshold logic
554
 
555
  A threshold gate computes a Boolean function by taking a weighted sum of binary inputs and comparing the result to a threshold; the output is 1 when the sum meets or exceeds the threshold and 0 otherwise. Equivalently, it is a neuron with Heaviside step activation, integer weights, and an integer bias.
 
722
 
723
  ```
724
  neural_computer.safetensors canonical model (32-bit, 64 KB, ~8.61M params)
725
+ variants/ 18 fitness variants + 5 standalone machines
726
+ (neural_subleq8, neural_rv32, neural_matrix8,
727
+ neural_subleq8io, neural_reflect)
728
  src/ the library (run scripts as `python src/<name>.py`)
729
  ├── build.py generator (one safetensors per invocation; also `subleq`, `rv32`)
730
  ├── quantize.py min integer dtypes + ternary verification/repair
 
734
  │ RV32 object loader, and the test suites (subleq / rv32 / rv32-c)
735
  ├── matrix8.py compiles the CPU to a recurrent ternary matrix stack; the
736
  │ exhaustive equality suite and the analog crossbar simulation
737
+ ── constructor8.py the neural_subleq8io host, the recipe codec, and the universal
738
+ constructor / self-reproduction suite
739
+ └── reflect.py neural_reflect: a threshold interpreter whose state holds its
740
+ own gate table; reference equality + self-modification suite
741
  tools/ build_all.py (build + quantize + verify every profile),
742
  cpu_programs.py (assembler + CPU program suite), test_cpu.py
743
  (program suite vs a variant), play.py (interactive demo),
src/eval_all.py CHANGED
@@ -623,6 +623,7 @@ MACHINE_VERIFIER = {
623
  "rv32": "machines.py",
624
  "matrix8": "matrix8.py",
625
  "subleq8io": "constructor8.py",
 
626
  }
627
 
628
 
 
623
  "rv32": "machines.py",
624
  "matrix8": "matrix8.py",
625
  "subleq8io": "constructor8.py",
626
+ "reflect": "reflect.py",
627
  }
628
 
629
 
src/reflect.py ADDED
@@ -0,0 +1,524 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """neural_reflect — a self-interpreting ternary threshold processor whose
2
+ transition is determined by weights stored in its own writable state.
3
+
4
+ Every other machine in the family has a fixed weight tensor: the weights are
5
+ what the machine is, the state is what it operates on, and the two never
6
+ touch. neural_reflect removes that separation. The state is a flat array of S
7
+ binary signals; a contiguous block of them (the NET region) encodes a netlist
8
+ of G threshold gates, and a fixed ternary interpreter U evaluates one stored
9
+ gate per recurrence:
10
+
11
+ 1. select gate gp (an interpreter register) from the stored table,
12
+ 2. read its two ternary input weights, integer bias, and two input
13
+ addresses and one output address (all addresses index the S signals),
14
+ 3. fetch the two addressed signal values (indirect read),
15
+ 4. compute out = H(w0*v0 + w1*v1 + bias),
16
+ 5. write out to the addressed signal (indirect write),
17
+ 6. gp = (gp + 1) mod G.
18
+
19
+ G recurrences evaluate the whole stored netlist once. Because the addresses
20
+ in step 5 may point into the NET region, a stored gate can overwrite the
21
+ encoding of another gate: the running program edits the weights that define
22
+ it (self-modification), and by rewriting the whole NET region it becomes a
23
+ different machine with no external constructor (metamorphosis). U is a fixed
24
+ ternary threshold circuit ({-1,0,1} weights, integer biases, Heaviside step),
25
+ compiled to a recurrent matrix stack by matrix8's compiler; on a memristive
26
+ crossbar the write in step 5 is a conductance change, so a program editing
27
+ NET is the computation editing the array's own weights.
28
+
29
+ The interpreter is verified against a reference in four ways: exhaustive
30
+ single-gate semantics, single-step agreement on random full states, lockstep
31
+ interpretation of loaded netlists (a composed XOR built from the family's
32
+ gates, and a program that rewrites its own gate table between sweeps), and
33
+ gate-graph vs matrix-form equality.
34
+
35
+ Usage:
36
+ python src/reflect.py build # compile + save variants/neural_reflect.safetensors
37
+ python src/reflect.py verify # reference equality + self-modification + matrix==gate
38
+ python src/reflect.py all
39
+ """
40
+
41
+ from __future__ import annotations
42
+
43
+ import argparse
44
+ import json
45
+ import os
46
+ import sys
47
+ import time
48
+ from typing import Dict, List, Tuple
49
+
50
+ import torch
51
+ from safetensors.torch import save_file
52
+
53
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
54
+ from matrix8 import Net, compile_net
55
+
56
+ REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
57
+ MODEL_PATH = os.path.join(REPO, "variants", "neural_reflect.safetensors")
58
+
59
+ # --- configuration (parametric; this instance is the shipped one) ----------
60
+ S = 256 # addressable signal bits (the whole RAM)
61
+ A = 8 # address width, 2**A == S
62
+ G = 6 # stored gates
63
+ F = 2 # inputs per gate (the family builds everything from <=2-in gates)
64
+ WB = 2 # weight-code bits per input
65
+ BB = 4 # bias bits (signed)
66
+ R = F * (A + WB) + BB + A # 32 bits per gate record
67
+ NET_BASE = S - G * R # 64: records occupy the high signals
68
+ HALT_SIG = NET_BASE - 1 # 63: a data bit the program raises to halt
69
+ GPW = 3 # gp register width (ceil(log2 G))
70
+ STATE_BITS = S + GPW + 1 # signals, gp, halt
71
+
72
+ assert (1 << A) == S and NET_BASE == 64 and R == 32
73
+
74
+ # record field offsets (MSB-first within the 32-bit record)
75
+ OFF_A0, OFF_W0 = 0, 8
76
+ OFF_A1, OFF_W1 = 10, 18
77
+ OFF_BIAS, OFF_OUT = 20, 24
78
+
79
+ WCODE = {1: (0, 1), -1: (1, 0), 0: (0, 0)} # (hi, lo), MSB-first
80
+
81
+
82
+ # =============================================================================
83
+ # bit helpers and netlist encoding
84
+ # =============================================================================
85
+
86
+ def to_bits(v: int, n: int) -> List[int]:
87
+ return [(v >> (n - 1 - i)) & 1 for i in range(n)]
88
+
89
+
90
+ def from_bits(bits: List[int]) -> int:
91
+ v = 0
92
+ for b in bits:
93
+ v = (v << 1) | int(b)
94
+ return v
95
+
96
+
97
+ def signed(bits: List[int]) -> int:
98
+ v = from_bits(bits)
99
+ return v - (1 << len(bits)) if bits[0] else v
100
+
101
+
102
+ def encode_gate(s0: Tuple[int, int], s1: Tuple[int, int], bias: int, out: int) -> List[int]:
103
+ """(addr, weight) per input, signed bias, output address -> 32-bit record."""
104
+ (a0, w0), (a1, w1) = s0, s1
105
+ rec = [0] * R
106
+ rec[OFF_A0:OFF_A0 + A] = to_bits(a0, A)
107
+ rec[OFF_W0:OFF_W0 + WB] = list(WCODE[w0])
108
+ rec[OFF_A1:OFF_A1 + A] = to_bits(a1, A)
109
+ rec[OFF_W1:OFF_W1 + WB] = list(WCODE[w1])
110
+ rec[OFF_BIAS:OFF_BIAS + BB] = to_bits(bias & ((1 << BB) - 1), BB)
111
+ rec[OFF_OUT:OFF_OUT + A] = to_bits(out, A)
112
+ return rec
113
+
114
+
115
+ NOOP = ((0, 0), (0, 0), -1, HALT_SIG - 1) # weights 0, bias<0 -> writes 0 to a scratch bit
116
+
117
+
118
+ def encode_netlist(gates: List[tuple]) -> List[int]:
119
+ """Pack up to G gate records into the NET region (returns G*R bits)."""
120
+ assert len(gates) <= G
121
+ padded = list(gates) + [NOOP] * (G - len(gates))
122
+ out: List[int] = []
123
+ for (s0, s1, bias, o) in padded:
124
+ out += encode_gate(s0, s1, bias, o)
125
+ return out
126
+
127
+
128
+ def make_state(signals: List[int], gp: int = 0, halt: int = 0,
129
+ netlist: List[tuple] | None = None) -> dict:
130
+ sig = list(signals) + [0] * (S - len(signals))
131
+ if netlist is not None:
132
+ sig[NET_BASE:S] = encode_netlist(netlist)
133
+ return {"sig": sig[:S], "gp": gp, "halt": int(halt)}
134
+
135
+
136
+ # =============================================================================
137
+ # reference interpreter (one stored gate per step)
138
+ # =============================================================================
139
+
140
+ def ref_step(st: dict) -> dict:
141
+ sig, gp, halt = list(st["sig"]), st["gp"], st["halt"]
142
+ if halt:
143
+ return {"sig": sig, "gp": gp, "halt": halt}
144
+ base = NET_BASE + gp * R
145
+ rec = sig[base:base + R]
146
+
147
+ def wval(hilo):
148
+ hi, lo = hilo
149
+ if (hi, lo) == (0, 1):
150
+ return 1
151
+ if (hi, lo) == (1, 0):
152
+ return -1
153
+ return 0
154
+
155
+ a0 = from_bits(rec[OFF_A0:OFF_A0 + A]); w0 = wval((rec[OFF_W0], rec[OFF_W0 + 1]))
156
+ a1 = from_bits(rec[OFF_A1:OFF_A1 + A]); w1 = wval((rec[OFF_W1], rec[OFF_W1 + 1]))
157
+ bias = signed(rec[OFF_BIAS:OFF_BIAS + BB])
158
+ oa = from_bits(rec[OFF_OUT:OFF_OUT + A])
159
+ acc = w0 * sig[a0] + w1 * sig[a1] + bias
160
+ out = 1 if acc >= 0 else 0
161
+ h = sig[HALT_SIG] # halt latches from the pre-write value, as U does
162
+ sig[oa] = out
163
+ ngp = 0 if gp == G - 1 else gp + 1
164
+ nhalt = 1 if (halt or h) else 0
165
+ return {"sig": sig, "gp": ngp, "halt": nhalt}
166
+
167
+
168
+ # =============================================================================
169
+ # the interpreter as a fixed ternary threshold circuit
170
+ # =============================================================================
171
+
172
+ def build_reflect_net() -> Tuple[Net, List[str], List[str]]:
173
+ net = Net()
174
+ sig = [f"s{i}" for i in range(S)]
175
+ gp = [f"gp{k}" for k in range(GPW)]
176
+ halt = "halt"
177
+ nhalt = net.NOT("nhalt", halt)
178
+
179
+ # gate select (gp one-hot over 0..G-1)
180
+ gp_oh = [net.DECODE(f"gpoh{g}", gp, g) for g in range(G)]
181
+
182
+ # read the selected record: recbit[r] = OR_g (gp_oh[g] AND NET[g,r])
183
+ recbit = []
184
+ for r in range(R):
185
+ terms = [net.AND(f"rb{r}g{g}", [gp_oh[g], sig[NET_BASE + g * R + r]]) for g in range(G)]
186
+ recbit.append(net.OR(f"rb{r}", terms))
187
+
188
+ a0 = recbit[OFF_A0:OFF_A0 + A]
189
+ w0 = (recbit[OFF_W0], recbit[OFF_W0 + 1])
190
+ a1 = recbit[OFF_A1:OFF_A1 + A]
191
+ w1 = (recbit[OFF_W1], recbit[OFF_W1 + 1])
192
+ biasbits = recbit[OFF_BIAS:OFF_BIAS + BB]
193
+ oabits = recbit[OFF_OUT:OFF_OUT + A]
194
+
195
+ # indirect reads: val_j = OR_s ( decode(addr_j)==s AND sig[s] )
196
+ def read(addrbits, tag):
197
+ terms = []
198
+ for s in range(S):
199
+ oh = net.DECODE(f"rd{tag}oh{s}", addrbits, s)
200
+ terms.append(net.AND(f"rd{tag}a{s}", [oh, sig[s]]))
201
+ return net.OR(f"rd{tag}", terms)
202
+
203
+ v0 = read(a0, "0")
204
+ v1 = read(a1, "1")
205
+
206
+ # ternary contribution c_j in {-1,0,1}, two's complement (bit0 magnitude, sign)
207
+ def contrib(w, v, tag):
208
+ hi, lo = w
209
+ pos = net.AND(f"pos{tag}", [net.NOT(f"nhi{tag}", hi), lo])
210
+ neg = net.AND(f"neg{tag}", [hi, net.NOT(f"nlo{tag}", lo)])
211
+ mag = net.OR(f"mag{tag}", [net.AND(f"tp{tag}", [pos, v]),
212
+ net.AND(f"tn{tag}", [neg, v])])
213
+ sign = net.AND(f"csgn{tag}", [neg, v])
214
+ return mag, sign
215
+
216
+ m0, sg0 = contrib(w0, v0, "0")
217
+ m1, sg1 = contrib(w1, v1, "1")
218
+
219
+ # acc = c0 + c1 + bias over 6-bit two's complement (LSB-first), out = acc >= 0
220
+ W6 = 6
221
+
222
+ def c_lsb(mag, sign):
223
+ return [mag] + [sign] * (W6 - 1)
224
+
225
+ def bias_lsb():
226
+ b = biasbits # MSB-first [b3(sign) b2 b1 b0]
227
+ lo = [b[3], b[2], b[1], b[0]] # b0..b3 LSB-first
228
+ return lo + [b[0]] * (W6 - BB) # sign-extend b3
229
+
230
+ def ripple(pfx, a_lsb, b_lsb):
231
+ c = "#0"
232
+ out = []
233
+ for k in range(W6):
234
+ s_, c = net.FA(f"{pfx}.fa{k}", a_lsb[k], b_lsb[k], c)
235
+ out.append(s_)
236
+ return out
237
+
238
+ s01 = ripple("acc0", c_lsb(m0, sg0), c_lsb(m1, sg1))
239
+ acc = ripple("acc1", s01, bias_lsb())
240
+ out = net.NOT("out", acc[W6 - 1]) # acc >= 0 <=> sign bit 0
241
+
242
+ # indirect write: sig'[s] = (decode(outaddr)==s AND nhalt) ? out : sig[s]
243
+ nsig = []
244
+ for s in range(S):
245
+ oh = net.DECODE(f"wroh{s}", oabits, s)
246
+ sel = net.AND(f"wrsel{s}", [oh, nhalt])
247
+ nsig.append(net.MUX(f"nsig{s}", sel, out, sig[s]))
248
+
249
+ # gp' = nhalt ? ((gp == G-1) ? 0 : gp+1) : gp (3-bit increment)
250
+ gp_lsb = [gp[GPW - 1 - k] for k in range(GPW)]
251
+ inc, c = [], "#1"
252
+ for k in range(GPW):
253
+ s_ = net.XOR(f"gpinc.x{k}", gp_lsb[k], c)
254
+ c = net.AND(f"gpinc.c{k}", [gp_lsb[k], c])
255
+ inc.append(s_)
256
+ inc_msb = [inc[GPW - 1 - k] for k in range(GPW)]
257
+ wrapped = [net.MUX(f"gpwrap{k}", gp_oh[G - 1], "#0", inc_msb[k]) for k in range(GPW)]
258
+ ngp = [net.MUX(f"ngp{k}", nhalt, wrapped[k], gp[k]) for k in range(GPW)]
259
+
260
+ nhalt_out = net.OR("halt_next", [halt, sig[HALT_SIG]])
261
+
262
+ outputs = nsig + ngp + [nhalt_out]
263
+ inputs = sig + gp + [halt]
264
+ return net, inputs, outputs
265
+
266
+
267
+ # =============================================================================
268
+ # gate-graph evaluator (independent of the matrix compile) for matrix==gate
269
+ # =============================================================================
270
+
271
+ def eval_net(net: Net, inputs: List[str], outputs: List[str],
272
+ values: Dict[str, int]) -> List[int]:
273
+ v = dict(values)
274
+ v["#0"], v["#1"] = 0, 1
275
+ indeg = {g: 0 for g in net.gates}
276
+ cons: Dict[str, List[str]] = {}
277
+ for g, (ins, _) in net.gates.items():
278
+ for s, _w in ins:
279
+ if s in net.gates:
280
+ indeg[g] += 1
281
+ cons.setdefault(s, []).append(g)
282
+ order = [g for g, d in indeg.items() if d == 0]
283
+ i = 0
284
+ while i < len(order):
285
+ for c in cons.get(order[i], []):
286
+ if c in net.gates:
287
+ indeg[c] -= 1
288
+ if indeg[c] == 0:
289
+ order.append(c)
290
+ i += 1
291
+ for g in order:
292
+ ins, b = net.gates[g]
293
+ acc = b + sum(w * v[s] for s, w in ins)
294
+ v[g] = 1 if acc >= 0 else 0
295
+ return [v[o] for o in outputs]
296
+
297
+
298
+ # =============================================================================
299
+ # state <-> vector, and the compiled matrix runtime
300
+ # =============================================================================
301
+
302
+ def state_to_vec(st: dict) -> torch.Tensor:
303
+ v = torch.zeros(STATE_BITS)
304
+ v[:S] = torch.tensor(st["sig"], dtype=torch.float32)
305
+ v[S:S + GPW] = torch.tensor(to_bits(st["gp"], GPW), dtype=torch.float32)
306
+ v[S + GPW] = float(st["halt"])
307
+ return v
308
+
309
+
310
+ def vec_to_state(v: torch.Tensor) -> dict:
311
+ b = [int(round(float(x))) for x in v.tolist()]
312
+ return {"sig": b[:S], "gp": from_bits(b[S:S + GPW]), "halt": b[S + GPW]}
313
+
314
+
315
+ class Reflect:
316
+ HALT_IDX = S + GPW
317
+
318
+ def __init__(self, layers, device="cpu"):
319
+ self.W = [W.to(device=device, dtype=torch.float32) for W, _ in layers]
320
+ self.B = [B.to(device=device, dtype=torch.float32) for _, B in layers]
321
+ self.device = device
322
+
323
+ def step(self, v: torch.Tensor) -> torch.Tensor:
324
+ for W, b in zip(self.W, self.B):
325
+ v = ((v @ W.T + b) >= 0).float()
326
+ return v
327
+
328
+ def run_steps(self, st: dict, n: int) -> dict:
329
+ v = state_to_vec(st).unsqueeze(0).to(self.device)
330
+ for _ in range(n):
331
+ v = self.step(v)
332
+ return vec_to_state(v[0].cpu())
333
+
334
+
335
+ # =============================================================================
336
+ # build
337
+ # =============================================================================
338
+
339
+ def build(save: bool = True):
340
+ print("Assembling the interpreter U from the verified cell library...")
341
+ t0 = time.perf_counter()
342
+ net, inputs, outputs = build_reflect_net()
343
+ layers, info = compile_net(net, inputs, outputs)
344
+ for W, B in layers:
345
+ assert set(torch.unique(W).tolist()) <= {-1, 0, 1}
346
+ print(f" {len(net.gates)} threshold gates -> {info['layers']} ternary matrices "
347
+ f"(max width {info['max_width']}, {info['total_weights']:,} weights) "
348
+ f"in {time.perf_counter() - t0:.1f}s")
349
+ if save:
350
+ tensors = {f"matrix.layer{i:03d}.weight": W for i, (W, _) in enumerate(layers)}
351
+ tensors.update({f"matrix.layer{i:03d}.bias": B for i, (_, B) in enumerate(layers)})
352
+ tensors["manifest.signals"] = torch.tensor([float(S)])
353
+ tensors["manifest.gates"] = torch.tensor([float(G)])
354
+ tensors["manifest.state_bits"] = torch.tensor([float(STATE_BITS)])
355
+ tensors["manifest.layers"] = torch.tensor([float(info["layers"])])
356
+ tensors["manifest.version"] = torch.tensor([1.0])
357
+ meta = {"machine": "reflect", "weight_quantization": "ternary",
358
+ "config": json.dumps({"S": S, "A": A, "G": G, "F": F,
359
+ "record_bits": R, "net_base": NET_BASE,
360
+ "halt_signal": HALT_SIG})}
361
+ save_file(tensors, MODEL_PATH, metadata=meta)
362
+ print(f" saved {MODEL_PATH} ({os.path.getsize(MODEL_PATH) / 1e6:.1f} MB)")
363
+ return net, inputs, outputs, layers
364
+
365
+
366
+ # =============================================================================
367
+ # demonstration netlists
368
+ # =============================================================================
369
+
370
+ def netlist_xor(a=0, b=1, out=4, t0=2, t1=3):
371
+ # XOR = AND( OR(a,b), NAND(a,b) ), all from the family's threshold cells.
372
+ return [((a, 1), (b, 1), -1, t0), # OR -> t0
373
+ ((a, -1), (b, -1), 1, t1), # NAND -> t1
374
+ ((t0, 1), (t1, 1), -2, out)] # AND -> out
375
+
376
+
377
+ def _bias_lsb_signal(gate_index: int) -> int:
378
+ # signal index of the bias LSB (b0) of gate `gate_index`
379
+ return NET_BASE + gate_index * R + OFF_BIAS + (BB - 1)
380
+
381
+
382
+ def netlist_selfmod(a=0, b=1, out=2):
383
+ # g0 computes AND(a,b)->out (bias -2). g1 writes 1 into g0's bias LSB,
384
+ # turning bias -2 (AND) into -1 (OR): the program edits the weights that
385
+ # define g0, so the interpreted function becomes OR on the next sweep.
386
+ return [((a, 1), (b, 1), -2, out), # g0: AND now, OR after edit
387
+ ((0, 0), (0, 0), 0, _bias_lsb_signal(0))] # g1: write 1 -> g0.bias LSB
388
+
389
+
390
+ # =============================================================================
391
+ # verify
392
+ # =============================================================================
393
+
394
+ def _rand_states(n, gen):
395
+ sig = (torch.rand(n, S, generator=gen) < 0.5).long()
396
+ gp = torch.randint(0, G, (n,), generator=gen)
397
+ return sig, gp
398
+
399
+
400
+ def verify(refl: Reflect, net, inputs, outputs, device) -> bool:
401
+ ok = True
402
+
403
+ # 1. exhaustive single-gate semantics: selected gate at gp=0, both inputs
404
+ # placed at fixed addresses, sweep the two ternary weights x both bias signs
405
+ # x all input assignments; U's write and out bit must match H(w0 v0 + w1 v1 + b).
406
+ print("[1/4] Exhaustive single-gate semantics "
407
+ "(all weights x biases x input assignments)")
408
+ bad = 0
409
+ total = 0
410
+ for w0 in (-1, 0, 1):
411
+ for w1 in (-1, 0, 1):
412
+ for bias in range(-(1 << (BB - 1)), (1 << (BB - 1))):
413
+ nl = [((10, w0), (11, w1), bias, 20)]
414
+ for va in (0, 1):
415
+ for vb in (0, 1):
416
+ st = make_state([0] * S, gp=0, netlist=nl)
417
+ st["sig"][10] = va
418
+ st["sig"][11] = vb
419
+ got = refl.run_steps(st, 1)
420
+ exp = ref_step(st)
421
+ total += 1
422
+ if got != exp or got["sig"][20] != (1 if w0 * va + w1 * vb + bias >= 0 else 0):
423
+ bad += 1
424
+ print(f" {'OK ' if bad == 0 else 'FAIL'} {total} gate configurations exact")
425
+ ok &= bad == 0
426
+
427
+ # 2. single step on random full states, plus halted fixed point
428
+ print("[2/4] Single step vs reference on random full states")
429
+ gen = torch.Generator().manual_seed(0x5EED)
430
+ sig, gp = _rand_states(3000, gen)
431
+ bad = 0
432
+ for i in range(sig.shape[0]):
433
+ st = {"sig": sig[i].tolist(), "gp": int(gp[i]), "halt": 0}
434
+ if refl.run_steps(st, 1) != ref_step(st):
435
+ bad += 1
436
+ print(f" {'OK ' if bad == 0 else 'FAIL'} 3,000 random states exact")
437
+ ok &= bad == 0
438
+ hbad = 0
439
+ for i in range(300):
440
+ st = {"sig": sig[i].tolist(), "gp": int(gp[i]), "halt": 1}
441
+ if refl.run_steps(st, 1) != st:
442
+ hbad += 1
443
+ print(f" {'OK ' if hbad == 0 else 'FAIL'} 300 halted states are fixed points")
444
+ ok &= hbad == 0
445
+
446
+ # 3. interpret a composed XOR, and a self-modifying program, in lockstep
447
+ print("[3/4] Interpretation lockstep (composed XOR; self-rewriting program)")
448
+ xbad = 0
449
+ for a in (0, 1):
450
+ for b in (0, 1):
451
+ st = make_state([0] * S, gp=0, netlist=netlist_xor())
452
+ st["sig"][0], st["sig"][1] = a, b
453
+ r = dict(st)
454
+ for k in range(G): # one sweep
455
+ st2 = refl.run_steps(st, 1)
456
+ r = ref_step(r)
457
+ if st2 != r:
458
+ xbad += 1
459
+ st = st2
460
+ if st["sig"][4] != (a ^ b):
461
+ xbad += 1
462
+ print(f" {'OK ' if xbad == 0 else 'FAIL'} XOR interpreted for all inputs "
463
+ f"(U == reference every step; s4 == a^b)")
464
+ ok &= xbad == 0
465
+
466
+ smbad = 0
467
+ for a in (0, 1):
468
+ for b in (0, 1):
469
+ st = make_state([0] * S, gp=0, netlist=netlist_selfmod())
470
+ st["sig"][0], st["sig"][1] = a, b
471
+ r = dict(st)
472
+ trace = []
473
+ for k in range(2 * G): # two sweeps
474
+ st = refl.run_steps(st, 1)
475
+ r = ref_step(r)
476
+ if st != r:
477
+ smbad += 1
478
+ if (k + 1) % G == 0:
479
+ trace.append(st["sig"][2])
480
+ # tick 1 computed AND, tick 2 (after the edit) computed OR
481
+ if trace != [a & b, a | b]:
482
+ smbad += 1
483
+ print(f" {'OK ' if smbad == 0 else 'FAIL'} self-modification: AND rewrites "
484
+ f"itself to OR mid-run (out = a&b then a|b), U == reference")
485
+ ok &= smbad == 0
486
+
487
+ # 4. gate graph == matrix form on random states
488
+ print("[4/4] Gate graph == matrix form")
489
+ gbad = 0
490
+ for i in range(400):
491
+ vals = {f"s{j}": int(sig[i, j]) for j in range(S)}
492
+ vals.update({f"gp{k}": to_bits(int(gp[i]), GPW)[k] for k in range(GPW)})
493
+ vals["halt"] = 0
494
+ gate_out = eval_net(net, inputs, outputs, vals)
495
+ v = torch.zeros(STATE_BITS)
496
+ for j in range(S):
497
+ v[j] = int(sig[i, j])
498
+ v[S:S + GPW] = torch.tensor(to_bits(int(gp[i]), GPW), dtype=torch.float32)
499
+ mat_out = refl.step(v.unsqueeze(0).to(device))[0].cpu().tolist()
500
+ if [int(round(x)) for x in mat_out] != gate_out:
501
+ gbad += 1
502
+ print(f" {'OK ' if gbad == 0 else 'FAIL'} 400 random states: matrix step "
503
+ f"== gate-graph step, bit for bit")
504
+ ok &= gbad == 0
505
+
506
+ print("\nREFLECT (interpreter == reference; matrix == gate):", "PASS" if ok else "FAIL")
507
+ return ok
508
+
509
+
510
+ def main() -> int:
511
+ ap = argparse.ArgumentParser(description="neural_reflect builder / verifier")
512
+ ap.add_argument("cmd", choices=["build", "verify", "all"])
513
+ ap.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
514
+ args = ap.parse_args()
515
+ net, inputs, outputs, layers = build(save=args.cmd in ("build", "all"))
516
+ rc = 0
517
+ if args.cmd in ("verify", "all"):
518
+ refl = Reflect(layers, device=args.device)
519
+ rc = 0 if verify(refl, net, inputs, outputs, args.device) else 1
520
+ return rc
521
+
522
+
523
+ if __name__ == "__main__":
524
+ sys.exit(main())
variants/neural_reflect.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:677c47e918126104af6c391b9f18eae30ee3759652548df144372b50abd9c81c
3
+ size 8166918