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 +63 -8
- src/eval_all.py +1 -0
- src/reflect.py +524 -0
- variants/neural_reflect.safetensors +3 -0
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 |
-
|
| 43 |
-
they carry the family from the smallest possible processor to
|
| 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.
|
| 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 +
|
| 674 |
-
(neural_subleq8, neural_rv32, neural_matrix8,
|
|
|
|
| 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 |
-
|
| 685 |
-
|
|
|
|
|
|
|
| 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
|