demos: standalone per-machine programs that put each machine to work
Browse filesA new demos/ folder, one script per machine class, each self-contained and
cross-checked against an independent reference:
- neural_computer8: self-modifying Sieve of Eratosthenes (all 54 primes < 256),
Euclid's GCD via DIV/MUL/SUB, Collatz step counts.
- neural_rv32: pi by Machin's formula to nine digits; ternary neural nets run as
NEUR software (one learned by gradient descent, one XOR compiled by hand).
- neural_matrix8: 65,536 CPUs stepped in lockstep as one batched matrix product.
- neural_attractor: factoring semiprimes by running a multiplier backward, and
6-/8-queens by relaxing toward zero unsatisfied clauses.
- neural_subleq8io: the universal constructor fabricates a sibling byte-for-byte
and boots it.
- neural_reflect: self-modifying SUBLEQ on a stored machine on the interpreter.
- neural_reversible: bijective mixing, exact reversal, and the counterfactual.
- neural_ca: a Loschmidt echo and a reversible-automaton block cipher.
- neural_tile: self-assembly grows Pascal mod 2, verified against Lucas' theorem.
- demos/README.md +22 -0
- demos/neural_attractor_factoring.py +55 -0
- demos/neural_attractor_nqueens.py +90 -0
- demos/neural_ca_loschmidt_echo.py +55 -0
- demos/neural_ca_reversible_cipher.py +84 -0
- demos/neural_computer8_collatz.py +76 -0
- demos/neural_computer8_euclid_gcd.py +63 -0
- demos/neural_computer8_self_modifying_sieve.py +76 -0
- demos/neural_matrix8_gpu_cpu_fleet.py +73 -0
- demos/neural_reflect_self_modifying_stack.py +87 -0
- demos/neural_reversible_counterfactual.py +49 -0
- demos/neural_rv32_machin_pi.py +85 -0
- demos/neural_rv32_neural_nets_via_neur.py +184 -0
- demos/neural_subleq8io_universal_constructor.py +73 -0
- demos/neural_tile_pascal_lucas.py +42 -0
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# demos
|
| 2 |
+
|
| 3 |
+
Standalone programs that put each machine in the family to work on a named
|
| 4 |
+
task. Every script is self-contained (`python demos/<name>.py`), loads the
|
| 5 |
+
shipped weights, and cross-checks its result against an independent reference.
|
| 6 |
+
|
| 7 |
+
| Demo | Machine | What it does |
|
| 8 |
+
|------|---------|--------------|
|
| 9 |
+
| `neural_computer8_self_modifying_sieve.py` | `neural_computer8` | Sieve of Eratosthenes; with no indexed addressing, the program rewrites the address bytes of its own LOAD/STORE. Finds all 54 primes < 256. |
|
| 10 |
+
| `neural_computer8_euclid_gcd.py` | `neural_computer8` | Euclid's GCD, with `a mod b` built from the DIV/MUL/SUB opcodes and an XOR register swap. |
|
| 11 |
+
| `neural_computer8_collatz.py` | `neural_computer8` | Collatz step counts; exact for every seed whose trajectory stays inside the 8-bit range. |
|
| 12 |
+
| `neural_rv32_machin_pi.py` | `neural_rv32` | pi to nine digits by Machin's 1706 arctangent formula, printed to the console. |
|
| 13 |
+
| `neural_rv32_neural_nets_via_neur.py` | `neural_rv32` | Ternary neural nets run as NEUR software: one learned by gradient descent, one (XOR) compiled by construction. |
|
| 14 |
+
| `neural_matrix8_gpu_cpu_fleet.py` | `neural_matrix8` | 65,536 CPUs stepped in lockstep on the GPU as one batched matrix product. |
|
| 15 |
+
| `neural_attractor_factoring.py` | `neural_attractor` | Factors semiprimes by relaxing an 8x8 multiplier's energy backward; also divides. |
|
| 16 |
+
| `neural_attractor_nqueens.py` | `neural_attractor` | Solves 6- and 8-queens by relaxing toward zero unsatisfied clauses. |
|
| 17 |
+
| `neural_subleq8io_universal_constructor.py` | `neural_subleq8io` | The universal constructor fabricates a sibling machine byte-for-byte, which then boots. |
|
| 18 |
+
| `neural_reflect_self_modifying_stack.py` | `neural_reflect` | Self-modifying SUBLEQ code, on a stored machine, on the fixed interpreter: three levels in one tensor. |
|
| 19 |
+
| `neural_reversible_counterfactual.py` | `neural_reversible` | Bijective mixing, exact reverse recovery, and the counterfactual when a digest bit is flipped. |
|
| 20 |
+
| `neural_ca_loschmidt_echo.py` | `neural_ca` | A ~2,000-particle gas mixed 500 steps and un-mixed exactly; one flipped cell corrupts half the past. |
|
| 21 |
+
| `neural_ca_reversible_cipher.py` | `neural_ca` | The reversible automaton as a block cipher: exact decryption with the right key, noise with a wrong one. |
|
| 22 |
+
| `neural_tile_pascal_lucas.py` | `neural_tile` | Self-assembly grows Pascal's triangle mod 2; every cell verified against Lucas' theorem. |
|
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_attractor -- factoring semiprimes by running a multiplier backward.
|
| 2 |
+
|
| 3 |
+
An 8x8 array multiplier compiles to a quadratic energy that is 0 exactly on its
|
| 4 |
+
truth table. Clamp the 16 product bits and relax the 16 input bits and the
|
| 5 |
+
network settles into a factorization: multiplication run in reverse. Clamp one
|
| 6 |
+
input as well and the same circuit performs division.
|
| 7 |
+
|
| 8 |
+
python demos/neural_attractor_factoring.py
|
| 9 |
+
"""
|
| 10 |
+
import os, sys, time
|
| 11 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 12 |
+
REPO = os.path.dirname(HERE)
|
| 13 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 14 |
+
from attractor import multiplier
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
if __name__ == "__main__":
|
| 18 |
+
c, io = multiplier(8)
|
| 19 |
+
print("neural_attractor: factoring by energy relaxation")
|
| 20 |
+
print("=" * 56)
|
| 21 |
+
print(f"8x8 array multiplier as an energy landscape: {len(c.gates)} gate "
|
| 22 |
+
f"gadgets, {c.n} wires, 16 free input bits\n")
|
| 23 |
+
|
| 24 |
+
ladder = [(3599, 59, 61), (9797, 97, 101), (32399, 179, 181), (57599, 239, 241)]
|
| 25 |
+
for N, pa, pb in ladder:
|
| 26 |
+
target = {io["prod"][k]: (N >> k) & 1 for k in range(16)}
|
| 27 |
+
t0, got = time.perf_counter(), None
|
| 28 |
+
for attempt in range(1, 8):
|
| 29 |
+
s = c.solve(io["xs"] + io["ys"], {io["zero"]: 0}, target,
|
| 30 |
+
sweeps=2500, restarts=12, seed=N * 7 + attempt)
|
| 31 |
+
if s is not None:
|
| 32 |
+
x = sum(s[io["xs"][k]] << k for k in range(8))
|
| 33 |
+
y = sum(s[io["ys"][k]] << k for k in range(8))
|
| 34 |
+
if x * y == N and x > 1 and y > 1:
|
| 35 |
+
got = (x, y, attempt); break
|
| 36 |
+
dt = time.perf_counter() - t0
|
| 37 |
+
if got:
|
| 38 |
+
x, y, att = got
|
| 39 |
+
tag = "the prime pair" if {x, y} == {pa, pb} else "valid factorization"
|
| 40 |
+
print(f" {N:5d} = {x:3d} x {y:3d} ({dt:4.1f}s, attempt {att}) [{tag}]")
|
| 41 |
+
else:
|
| 42 |
+
print(f" {N:5d}: ground state not reached in budget ({dt:.1f}s)")
|
| 43 |
+
|
| 44 |
+
# division: clamp one input and the product, relax the other input
|
| 45 |
+
N, xa = 57599, 241
|
| 46 |
+
fixed = {io["zero"]: 0}
|
| 47 |
+
for k in range(8):
|
| 48 |
+
fixed[io["xs"][k]] = (xa >> k) & 1
|
| 49 |
+
target = {io["prod"][k]: (N >> k) & 1 for k in range(16)}
|
| 50 |
+
t0 = time.perf_counter()
|
| 51 |
+
s = c.solve(io["ys"], fixed, target, sweeps=800, restarts=10, seed=7)
|
| 52 |
+
if s is not None:
|
| 53 |
+
y = sum(s[io["ys"][k]] << k for k in range(8))
|
| 54 |
+
print(f"\n division mode: clamp x=241 and the product -> y = {y} "
|
| 55 |
+
f"({time.perf_counter()-t0:.1f}s; {xa} x {y} = {xa * y})")
|
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_attractor -- the N-queens problem solved by energy relaxation.
|
| 2 |
+
|
| 3 |
+
The attractor computer has no clock and no program counter: a circuit compiles
|
| 4 |
+
to a quadratic energy whose global minimum is its consistent assignment, and
|
| 5 |
+
"running" it is rolling downhill. Here every N-queens constraint (one queen per
|
| 6 |
+
row, no two sharing a column or diagonal) becomes a clause wire that must be 1;
|
| 7 |
+
the annealer relaxes the board variables toward zero unsatisfied clauses. This
|
| 8 |
+
is the min-conflicts / MAX-SAT objective, expressed entirely in the family's
|
| 9 |
+
Heaviside threshold gadgets.
|
| 10 |
+
|
| 11 |
+
python demos/neural_attractor_nqueens.py
|
| 12 |
+
"""
|
| 13 |
+
import os, sys, time
|
| 14 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 15 |
+
REPO = os.path.dirname(HERE)
|
| 16 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 17 |
+
from attractor import Circuit
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def queens_circuit(N):
|
| 21 |
+
"""Build the N-queens CNF as a threshold circuit; return every clause wire
|
| 22 |
+
so the solver can drive them all to 1 (a graded, per-clause objective)."""
|
| 23 |
+
c = Circuit()
|
| 24 |
+
v = {(r, col): c.wire() for r in range(N) for col in range(N)}
|
| 25 |
+
clauses = []
|
| 26 |
+
|
| 27 |
+
def OR_lits(lits):
|
| 28 |
+
acc = lits[0]
|
| 29 |
+
for w in lits[1:]:
|
| 30 |
+
acc = c.OR(acc, w)
|
| 31 |
+
return acc
|
| 32 |
+
|
| 33 |
+
for r in range(N): # each row: >= 1 queen
|
| 34 |
+
clauses.append(OR_lits([v[(r, col)] for col in range(N)]))
|
| 35 |
+
for c1 in range(N): # each row: <= 1 queen
|
| 36 |
+
for c2 in range(c1 + 1, N):
|
| 37 |
+
clauses.append(OR_lits([c.NOT(v[(r, c1)]), c.NOT(v[(r, c2)])]))
|
| 38 |
+
for col in range(N): # each column: <= 1
|
| 39 |
+
for r1 in range(N):
|
| 40 |
+
for r2 in range(r1 + 1, N):
|
| 41 |
+
clauses.append(OR_lits([c.NOT(v[(r1, col)]), c.NOT(v[(r2, col)])]))
|
| 42 |
+
cells = [(r, col) for r in range(N) for col in range(N)]
|
| 43 |
+
for i in range(len(cells)): # each diagonal: <= 1
|
| 44 |
+
for j in range(i + 1, len(cells)):
|
| 45 |
+
(r1, c1), (r2, c2) = cells[i], cells[j]
|
| 46 |
+
if r1 - c1 == r2 - c2 or r1 + c1 == r2 + c2:
|
| 47 |
+
clauses.append(OR_lits([c.NOT(v[(r1, c1)]), c.NOT(v[(r2, c2)])]))
|
| 48 |
+
return c, v, clauses
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def valid(board, N):
|
| 52 |
+
qs = [rc for rc, b in board.items() if b]
|
| 53 |
+
if len(qs) != N:
|
| 54 |
+
return False
|
| 55 |
+
for i in range(len(qs)):
|
| 56 |
+
for j in range(i + 1, len(qs)):
|
| 57 |
+
(r1, c1), (r2, c2) = qs[i], qs[j]
|
| 58 |
+
if r1 == r2 or c1 == c2 or abs(r1 - r2) == abs(c1 - c2):
|
| 59 |
+
return False
|
| 60 |
+
return True
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def solve_board(N, seeds=8):
|
| 64 |
+
c, v, clauses = queens_circuit(N)
|
| 65 |
+
free = list(v.values())
|
| 66 |
+
target = {w: 1 for w in clauses}
|
| 67 |
+
t0 = time.perf_counter()
|
| 68 |
+
for seed in range(seeds):
|
| 69 |
+
s = c.solve(free, {}, target, sweeps=6000, restarts=6, seed=seed)
|
| 70 |
+
if s is not None:
|
| 71 |
+
board = {rc: s[w] for rc, w in v.items()}
|
| 72 |
+
if valid(board, N):
|
| 73 |
+
return board, len(clauses), c.n, time.perf_counter() - t0
|
| 74 |
+
return None, len(clauses), c.n, time.perf_counter() - t0
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
if __name__ == "__main__":
|
| 78 |
+
print("neural_attractor: N-queens as energy relaxation")
|
| 79 |
+
print("=" * 56)
|
| 80 |
+
for N in (6, 8):
|
| 81 |
+
board, nclauses, nwires, dt = solve_board(N)
|
| 82 |
+
status = "SOLVED" if board else "no solution reached in budget"
|
| 83 |
+
print(f"\n{N}-queens: {nclauses} clause gadgets over {nwires} wires -> "
|
| 84 |
+
f"{status} ({dt:.1f}s)")
|
| 85 |
+
if board:
|
| 86 |
+
for r in range(N):
|
| 87 |
+
print(" " + " ".join("Q" if board[(r, col)] else "." for col in range(N)))
|
| 88 |
+
print(f" verified: {N} queens, no shared row/column/diagonal")
|
| 89 |
+
print("\nNo search loop was written: the constraints are an energy surface and")
|
| 90 |
+
print("the solution is its floor. The queens fall into place.")
|
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_ca -- a Loschmidt echo in a reversible cellular automaton.
|
| 2 |
+
|
| 3 |
+
The Margolus block rule is a bijection, so a gas of ~2,000 particles mixed for
|
| 4 |
+
500 steps can be un-mixed by iterating the same rule backward: the initial
|
| 5 |
+
configuration returns cell-for-cell (particle number conserved throughout). Yet
|
| 6 |
+
flipping a single cell of the mixed state before reversing corrupts roughly half
|
| 7 |
+
the reconstructed past -- exact reversibility and sensitive dependence in the
|
| 8 |
+
same automaton.
|
| 9 |
+
|
| 10 |
+
python demos/neural_ca_loschmidt_echo.py
|
| 11 |
+
"""
|
| 12 |
+
import os, sys, time, random, statistics
|
| 13 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 14 |
+
REPO = os.path.dirname(HERE)
|
| 15 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 16 |
+
import ca
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def coarse(g, H, W, k=8):
|
| 20 |
+
out = []
|
| 21 |
+
for by in range(0, H, k):
|
| 22 |
+
for bx in range(0, W, k):
|
| 23 |
+
out.append(sum(g[y][x] for y in range(by, by + k) for x in range(bx, bx + k)))
|
| 24 |
+
return out
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
if __name__ == "__main__":
|
| 28 |
+
H = W = 64
|
| 29 |
+
rng = random.Random(2026)
|
| 30 |
+
grid = [[1 if rng.random() < 0.5 else 0 for _ in range(W)] for _ in range(H)]
|
| 31 |
+
n0 = sum(map(sum, grid))
|
| 32 |
+
STEPS = 500
|
| 33 |
+
|
| 34 |
+
print("neural_ca: Loschmidt echo (mix, then run time backward)")
|
| 35 |
+
print("=" * 56)
|
| 36 |
+
t0 = time.perf_counter()
|
| 37 |
+
fwd = ca.run(grid, STEPS, 0)
|
| 38 |
+
n1 = sum(map(sum, fwd))
|
| 39 |
+
back = ca.run_back(fwd, STEPS, 0)
|
| 40 |
+
echo = back == grid
|
| 41 |
+
dt = time.perf_counter() - t0
|
| 42 |
+
|
| 43 |
+
print(f"particles: {n0} at t=0, {n1} at t={STEPS} "
|
| 44 |
+
f"({'conserved' if n0 == n1 else 'NOT CONSERVED'})")
|
| 45 |
+
print(f"coarse 8x8 occupancy stdev: t=0 {statistics.pstdev(coarse(grid, H, W)):.2f} "
|
| 46 |
+
f"-> t={STEPS} {statistics.pstdev(coarse(fwd, H, W)):.2f} (mixed)")
|
| 47 |
+
print(f"{STEPS} steps forward + {STEPS} reversed in {dt:.1f}s: "
|
| 48 |
+
f"t=0 recovered {'EXACTLY' if echo else 'FAILED'}")
|
| 49 |
+
|
| 50 |
+
flip = [row[:] for row in fwd]
|
| 51 |
+
flip[0][0] ^= 1
|
| 52 |
+
back2 = ca.run_back(flip, STEPS, 0)
|
| 53 |
+
ham = sum(back2[y][x] != grid[y][x] for y in range(H) for x in range(W))
|
| 54 |
+
print(f"butterfly: flip ONE cell at t={STEPS}, reverse again -> reconstructed "
|
| 55 |
+
f"past wrong in {ham}/{H * W} cells")
|
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_ca -- a reversible cellular automaton as a perfect cipher.
|
| 2 |
+
|
| 3 |
+
The Margolus block rule is a bijection of the lattice, so iterating it forward
|
| 4 |
+
diffuses a bitmap into noise and iterating the exact same rule backward restores
|
| 5 |
+
it bit-for-bit. That makes the automaton a block cipher whose key is the pair
|
| 6 |
+
(number of steps, starting partition phase): the right key inverts the diffusion
|
| 7 |
+
perfectly, and a key off by a single step returns noise. No information is ever
|
| 8 |
+
destroyed, so decryption is exact rather than approximate.
|
| 9 |
+
|
| 10 |
+
python demos/neural_ca_reversible_cipher.py
|
| 11 |
+
"""
|
| 12 |
+
import os, sys
|
| 13 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 14 |
+
REPO = os.path.dirname(HERE)
|
| 15 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 16 |
+
import ca
|
| 17 |
+
|
| 18 |
+
# a recognizable plaintext bitmap (a heart), padded into a larger lattice
|
| 19 |
+
ART = [
|
| 20 |
+
" ### ### ",
|
| 21 |
+
" ##### ##### ",
|
| 22 |
+
"#############",
|
| 23 |
+
"#############",
|
| 24 |
+
"#############",
|
| 25 |
+
" ########### ",
|
| 26 |
+
" ######### ",
|
| 27 |
+
" ####### ",
|
| 28 |
+
" ##### ",
|
| 29 |
+
" ### ",
|
| 30 |
+
" # ",
|
| 31 |
+
]
|
| 32 |
+
PAD_X, PAD_Y = 6, 4
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def make_grid():
|
| 36 |
+
# the Margolus partition tiles 2x2 blocks toroidally, so H and W must be even
|
| 37 |
+
h = len(ART) + 2 * PAD_Y
|
| 38 |
+
w = len(ART[0]) + 2 * PAD_X
|
| 39 |
+
h += h & 1
|
| 40 |
+
w += w & 1
|
| 41 |
+
g = [[0] * w for _ in range(h)]
|
| 42 |
+
for y, row in enumerate(ART):
|
| 43 |
+
for x, ch in enumerate(row):
|
| 44 |
+
if ch == "#":
|
| 45 |
+
g[y + PAD_Y][x + PAD_X] = 1
|
| 46 |
+
return g
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def render(g):
|
| 50 |
+
return "\n".join("".join("#" if c else "." for c in row) for row in g)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def hamming(a, b):
|
| 54 |
+
return sum(a[y][x] != b[y][x] for y in range(len(a)) for x in range(len(a[0])))
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
if __name__ == "__main__":
|
| 58 |
+
plain = make_grid()
|
| 59 |
+
KEY_STEPS, KEY_PHASE = 200, 0
|
| 60 |
+
|
| 61 |
+
cipher = ca.run(plain, KEY_STEPS, KEY_PHASE)
|
| 62 |
+
recovered = ca.run_back(cipher, KEY_STEPS, KEY_PHASE)
|
| 63 |
+
wrong = ca.run_back(cipher, KEY_STEPS - 1, KEY_PHASE) # key off by one step
|
| 64 |
+
|
| 65 |
+
n = sum(map(sum, plain))
|
| 66 |
+
print("neural_ca: reversible-automaton cipher")
|
| 67 |
+
print("=" * 46)
|
| 68 |
+
print(f"key = ({KEY_STEPS} steps, phase {KEY_PHASE}); {n} set bits (conserved "
|
| 69 |
+
f"throughout: {sum(map(sum, cipher)) == n})\n")
|
| 70 |
+
|
| 71 |
+
print("PLAINTEXT:")
|
| 72 |
+
print(render(plain))
|
| 73 |
+
print(f"\nCIPHERTEXT after {KEY_STEPS} forward steps (diffused to noise):")
|
| 74 |
+
print(render(cipher))
|
| 75 |
+
print(f"\nDECRYPTED with the correct key:")
|
| 76 |
+
print(render(recovered))
|
| 77 |
+
print(f" exact recovery: {recovered == plain} (Hamming distance 0)")
|
| 78 |
+
print(f"\nDECRYPTED with a wrong key (off by one step):")
|
| 79 |
+
print(render(wrong))
|
| 80 |
+
print(f" still scrambled: {hamming(wrong, plain)} of {len(plain)*len(plain[0])} "
|
| 81 |
+
f"cells differ from the plaintext")
|
| 82 |
+
print("\nEvery step is a bijection, so the ciphertext holds exactly the")
|
| 83 |
+
print("information of the plaintext -- no more, no less. Only the exact")
|
| 84 |
+
print("reverse trajectory recovers it.")
|
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_computer8 -- the Collatz map iterated on the threshold CPU.
|
| 2 |
+
|
| 3 |
+
n -> n/2 if even, 3n+1 if odd, counting steps to 1. The parity test is a
|
| 4 |
+
bitwise AND with 1 that leaves the flags untouched, so the loop branches on the
|
| 5 |
+
comparison that precedes it. The register file is architecturally 8-bit, so the
|
| 6 |
+
machine computes the Collatz trajectory modulo 256; for seeds whose true
|
| 7 |
+
trajectory never exceeds 255, that is the exact Collatz step count, checked here
|
| 8 |
+
against a native reference.
|
| 9 |
+
|
| 10 |
+
python demos/neural_computer8_collatz.py
|
| 11 |
+
"""
|
| 12 |
+
import os, sys, time
|
| 13 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 14 |
+
REPO = os.path.dirname(HERE)
|
| 15 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 16 |
+
sys.path.insert(0, os.path.join(REPO, "tools"))
|
| 17 |
+
from safetensors import safe_open
|
| 18 |
+
from cpu_programs import Asm
|
| 19 |
+
from eval_all import GenericThresholdCPU, get_manifest
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def build(n0):
|
| 23 |
+
a = Asm(1024)
|
| 24 |
+
a.load(0, "N"); a.load(1, "ONE"); a.xor(2, 2) # R0=n, R1=1, R2=steps
|
| 25 |
+
a.label("loop")
|
| 26 |
+
a.cmp(0, 1); a.jz("done") # n == 1 ?
|
| 27 |
+
a.xor(3, 3); a.add(3, 0); a.and_(3, 1) # R3 = n & 1 (flags preserved)
|
| 28 |
+
a.cmp(3, 1); a.jz("odd")
|
| 29 |
+
a.shr(0); a.jmp("step") # even: n >>= 1
|
| 30 |
+
a.label("odd")
|
| 31 |
+
a.xor(3, 3); a.add(3, 0); a.shl(3) # R3 = 2n
|
| 32 |
+
a.add(0, 3); a.add(0, 1) # n = 3n + 1
|
| 33 |
+
a.label("step")
|
| 34 |
+
a.add(2, 1); a.jmp("loop") # steps++
|
| 35 |
+
a.label("done")
|
| 36 |
+
a.store(2, "RES"); a.halt()
|
| 37 |
+
a.org(0x300); a.label("N"); a.db(n0)
|
| 38 |
+
a.label("ONE"); a.db(1); a.label("RES"); a.db(0)
|
| 39 |
+
return a, a.assemble()
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def true_collatz(n):
|
| 43 |
+
peak, steps = n, 0
|
| 44 |
+
while n != 1:
|
| 45 |
+
n = n // 2 if n % 2 == 0 else 3 * n + 1
|
| 46 |
+
peak = max(peak, n); steps += 1
|
| 47 |
+
return steps, peak
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
if __name__ == "__main__":
|
| 51 |
+
tens = {}
|
| 52 |
+
with safe_open(os.path.join(REPO, "variants", "neural_computer8_small.safetensors"),
|
| 53 |
+
framework="pt") as f:
|
| 54 |
+
for name in f.keys():
|
| 55 |
+
tens[name] = f.get_tensor(name).float()
|
| 56 |
+
cpu = GenericThresholdCPU(tens)
|
| 57 |
+
addr = get_manifest(tens)["addr_bits"]
|
| 58 |
+
|
| 59 |
+
print("neural_computer8: Collatz step counts through the gates")
|
| 60 |
+
print("=" * 56)
|
| 61 |
+
print(" seed CPU steps true steps peak in 8-bit range?")
|
| 62 |
+
for n0 in (6, 7, 9, 18, 25, 45):
|
| 63 |
+
a, mem = build(n0)
|
| 64 |
+
state = {"pc": 0, "regs": [0] * 4, "flags": [0] * 4, "mem": list(mem),
|
| 65 |
+
"halted": False, "sp": (1 << addr) - 1}
|
| 66 |
+
t0 = time.perf_counter()
|
| 67 |
+
final, cycles = cpu.run(state, max_cycles=3000)
|
| 68 |
+
steps = final["mem"][a.labels["RES"]]
|
| 69 |
+
tsteps, peak = true_collatz(n0)
|
| 70 |
+
inrange = peak <= 255
|
| 71 |
+
match = "MATCH" if (inrange and steps == tsteps) else \
|
| 72 |
+
("mod-256" if not inrange else "MISMATCH")
|
| 73 |
+
print(f" {n0:4d} {steps:9d} {tsteps:10d} {peak:4d} "
|
| 74 |
+
f"{'yes' if inrange else 'no (wraps)':10s} [{match}]")
|
| 75 |
+
print("\n For every seed whose trajectory stays under 256 the threshold CPU")
|
| 76 |
+
print(" reproduces the exact Collatz step count.")
|
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_computer8 -- Euclid's algorithm on the threshold CPU.
|
| 2 |
+
|
| 3 |
+
The greatest common divisor by repeated remainder, where the remainder itself
|
| 4 |
+
is synthesized from the machine's own opcodes: a mod b = a - (a / b) * b, using
|
| 5 |
+
the DIV, MUL and SUB gates, and the register swap is three XORs (no temporary).
|
| 6 |
+
Every step runs through threshold neurons.
|
| 7 |
+
|
| 8 |
+
python demos/neural_computer8_euclid_gcd.py
|
| 9 |
+
"""
|
| 10 |
+
import os, sys, time, math
|
| 11 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 12 |
+
REPO = os.path.dirname(HERE)
|
| 13 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 14 |
+
sys.path.insert(0, os.path.join(REPO, "tools"))
|
| 15 |
+
from safetensors import safe_open
|
| 16 |
+
from cpu_programs import Asm
|
| 17 |
+
from eval_all import GenericThresholdCPU, get_manifest
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def build(a0, b0):
|
| 21 |
+
g = Asm(1024)
|
| 22 |
+
g.load(0, "A") # R0 = a
|
| 23 |
+
g.load(1, "B") # R1 = b
|
| 24 |
+
g.xor(3, 3) # R3 = 0
|
| 25 |
+
g.label("loop")
|
| 26 |
+
g.cmp(1, 3); g.jz("done") # while b != 0
|
| 27 |
+
g.xor(2, 2); g.add(2, 0) # R2 = a
|
| 28 |
+
g._alu(0x8, 2, 1) # R2 = a / b (DIV opcode)
|
| 29 |
+
g.mul(2, 1) # R2 = (a/b)*b
|
| 30 |
+
g.sub(0, 2) # R0 = a mod b
|
| 31 |
+
g.xor(0, 1); g.xor(1, 0); g.xor(0, 1) # swap -> (b, a mod b)
|
| 32 |
+
g.jmp("loop")
|
| 33 |
+
g.label("done")
|
| 34 |
+
g.store(0, "RES"); g.halt()
|
| 35 |
+
g.org(0x300)
|
| 36 |
+
g.label("A"); g.db(a0)
|
| 37 |
+
g.label("B"); g.db(b0)
|
| 38 |
+
g.label("RES"); g.db(0)
|
| 39 |
+
return g, g.assemble()
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
if __name__ == "__main__":
|
| 43 |
+
tens = {}
|
| 44 |
+
with safe_open(os.path.join(REPO, "variants", "neural_computer8_small.safetensors"),
|
| 45 |
+
framework="pt") as f:
|
| 46 |
+
for name in f.keys():
|
| 47 |
+
tens[name] = f.get_tensor(name).float()
|
| 48 |
+
cpu = GenericThresholdCPU(tens)
|
| 49 |
+
addr = get_manifest(tens)["addr_bits"]
|
| 50 |
+
|
| 51 |
+
print("neural_computer8: Euclid's GCD through the gates")
|
| 52 |
+
print("=" * 56)
|
| 53 |
+
for a0, b0 in [(84, 36), (120, 90), (221, 34), (255, 128)]:
|
| 54 |
+
g, mem = build(a0, b0)
|
| 55 |
+
state = {"pc": 0, "regs": [0] * 4, "flags": [0] * 4, "mem": list(mem),
|
| 56 |
+
"halted": False, "sp": (1 << addr) - 1}
|
| 57 |
+
t0 = time.perf_counter()
|
| 58 |
+
final, cycles = cpu.run(state, max_cycles=400)
|
| 59 |
+
res = final["mem"][g.labels["RES"]]
|
| 60 |
+
ok = res == math.gcd(a0, b0)
|
| 61 |
+
print(f" gcd({a0:3d}, {b0:3d}) = {res:3d} ({cycles:3d} cycles, "
|
| 62 |
+
f"{time.perf_counter()-t0:.1f}s) "
|
| 63 |
+
f"[{'MATCH' if ok else 'MISMATCH vs ' + str(math.gcd(a0, b0))}]")
|
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_computer8 -- the Sieve of Eratosthenes with self-modifying code.
|
| 2 |
+
|
| 3 |
+
The threshold CPU's ISA has no indexed addressing, so the program does exactly
|
| 4 |
+
what memory-constrained 1970s code did: it rewrites the address bytes of its own
|
| 5 |
+
LOAD and STORE instructions before each access, walking a pointer across the
|
| 6 |
+
flags array. Every gate that fetches, decodes, marks, and patches is a threshold
|
| 7 |
+
neuron. It halts with all 54 primes below 256 marked.
|
| 8 |
+
|
| 9 |
+
python demos/neural_computer8_self_modifying_sieve.py
|
| 10 |
+
"""
|
| 11 |
+
import os, sys, time
|
| 12 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 13 |
+
REPO = os.path.dirname(HERE)
|
| 14 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 15 |
+
sys.path.insert(0, os.path.join(REPO, "tools"))
|
| 16 |
+
from safetensors import safe_open
|
| 17 |
+
from cpu_programs import Asm, _enc
|
| 18 |
+
from eval_all import GenericThresholdCPU, get_manifest
|
| 19 |
+
|
| 20 |
+
FLAGS = 0x300 # flags[0..255] at 0x300..0x3FF
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def build():
|
| 24 |
+
g = Asm(1024)
|
| 25 |
+
g.load(3, "ONE") # R3 = 1 (mark value / increment)
|
| 26 |
+
g.load(0, "TWO") # R0 = p = 2
|
| 27 |
+
g.label("outer")
|
| 28 |
+
g.store(0, "Rlo") # patch probe address low byte <- p
|
| 29 |
+
g.dw(_enc(0xA, 2, 0)) # probe: R2 = flags[p] (self-modified)
|
| 30 |
+
g.db(FLAGS >> 8); g.label("Rlo"); g.db(0x00)
|
| 31 |
+
g.cmp(2, 3); g.jz("next_p") # flags[p] == 1 -> composite, skip
|
| 32 |
+
g.xor(1, 1); g.add(1, 0); g.add(1, 0) # m = 2p
|
| 33 |
+
g.label("inner")
|
| 34 |
+
g.store(1, "Wlo") # patch mark address low byte <- m
|
| 35 |
+
g.dw(_enc(0xB, 0, 3)) # mark: flags[m] = R3 = 1 (self-modified)
|
| 36 |
+
g.db(FLAGS >> 8); g.label("Wlo"); g.db(0x00)
|
| 37 |
+
g.add(1, 0) # m += p (carry iff we passed 255)
|
| 38 |
+
g.jnc("inner")
|
| 39 |
+
g.label("next_p")
|
| 40 |
+
g.add(0, 3) # p += 1
|
| 41 |
+
g.load(2, "SIXTEEN")
|
| 42 |
+
g.cmp(0, 2)
|
| 43 |
+
g.jnz("outer") # loop until p == 16 (16^2 > 255)
|
| 44 |
+
g.halt()
|
| 45 |
+
g.org(0x200)
|
| 46 |
+
g.label("ONE"); g.db(1)
|
| 47 |
+
g.label("TWO"); g.db(2)
|
| 48 |
+
g.label("SIXTEEN"); g.db(16)
|
| 49 |
+
return g, g.assemble()
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
if __name__ == "__main__":
|
| 53 |
+
g, mem = build()
|
| 54 |
+
tens = {}
|
| 55 |
+
with safe_open(os.path.join(REPO, "variants", "neural_computer8_small.safetensors"),
|
| 56 |
+
framework="pt") as f:
|
| 57 |
+
for name in f.keys():
|
| 58 |
+
tens[name] = f.get_tensor(name).float()
|
| 59 |
+
cpu = GenericThresholdCPU(tens)
|
| 60 |
+
state = {"pc": 0, "regs": [0] * 4, "flags": [0] * 4, "mem": list(mem),
|
| 61 |
+
"halted": False, "sp": (1 << get_manifest(tens)["addr_bits"]) - 1}
|
| 62 |
+
t0 = time.perf_counter()
|
| 63 |
+
final, cycles = cpu.run(state, max_cycles=4000)
|
| 64 |
+
dt = time.perf_counter() - t0
|
| 65 |
+
|
| 66 |
+
got = [n for n in range(2, 256) if final["mem"][FLAGS + n] == 0]
|
| 67 |
+
ref = [n for n in range(2, 256) if all(n % d for d in range(2, int(n**0.5) + 1))]
|
| 68 |
+
print("neural_computer8: self-modifying Sieve of Eratosthenes")
|
| 69 |
+
print("=" * 56)
|
| 70 |
+
print(f"halted={final['halted']} {cycles} cycles through the gates ({dt:.0f}s)")
|
| 71 |
+
print(f"primes < 256 found: {len(got)} native sieve: {len(ref)} "
|
| 72 |
+
f"{'EXACT MATCH' if got == ref else 'MISMATCH'}")
|
| 73 |
+
print(" " + " ".join(map(str, got)))
|
| 74 |
+
print(f"self-modified operand bytes at halt: probe={final['mem'][g.labels['Rlo']]}, "
|
| 75 |
+
f"mark={final['mem'][g.labels['Wlo']]} (the program rewrote its own "
|
| 76 |
+
f"instruction stream as it ran)")
|
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_matrix8 -- a fleet of processors as one batched matrix product.
|
| 2 |
+
|
| 3 |
+
The whole CPU is compiled to 108 ternary weight matrices with a Heaviside step
|
| 4 |
+
between them, so one clock cycle is one matrix-vector product plus a threshold.
|
| 5 |
+
Add a batch dimension and N processors are the same 108 matmuls: here 65,536
|
| 6 |
+
independent CPUs step in lockstep on the GPU. A 256-wide correctness fleet
|
| 7 |
+
confirms every CPU halts exactly at cycle 2n+1 for its own countdown input.
|
| 8 |
+
|
| 9 |
+
python demos/neural_matrix8_gpu_cpu_fleet.py
|
| 10 |
+
"""
|
| 11 |
+
import os, sys, time
|
| 12 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 13 |
+
REPO = os.path.dirname(HERE)
|
| 14 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 15 |
+
import torch
|
| 16 |
+
from matrix8 import MatrixMachine, state_to_vec, _mk_state, _instr
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def prog_bytes():
|
| 20 |
+
return (_instr(0x1, 0, 1) # SUB R0, R1
|
| 21 |
+
+ _instr(0xD, 0, 0, 1) + [0, 0] # JNZ 0x0000 (address word)
|
| 22 |
+
+ _instr(0xF) # HALT
|
| 23 |
+
+ [0] * 8)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
if __name__ == "__main__":
|
| 27 |
+
dev = "cuda" if torch.cuda.is_available() else "cpu"
|
| 28 |
+
mm = MatrixMachine.from_file(device=dev)
|
| 29 |
+
n_neurons = sum(int(b.numel()) for b in mm.B)
|
| 30 |
+
name = torch.cuda.get_device_name(0) if dev == "cuda" else "cpu"
|
| 31 |
+
print("neural_matrix8: 65,536 CPUs as one batched matrix product")
|
| 32 |
+
print("=" * 56)
|
| 33 |
+
print(f"loaded {len(mm.W)} ternary matrices, {n_neurons:,} threshold neurons "
|
| 34 |
+
f"per transition, device={dev} ({name})")
|
| 35 |
+
mem = prog_bytes()
|
| 36 |
+
|
| 37 |
+
# correctness fleet: one CPU per 8-bit input, all 256 at once
|
| 38 |
+
V = torch.stack([state_to_vec(_mk_state(mem=mem, regs=(x, 1, 0, 0)))
|
| 39 |
+
for x in range(256)]).to(dev)
|
| 40 |
+
first_halt = torch.full((256,), -1, dtype=torch.long)
|
| 41 |
+
step = 0
|
| 42 |
+
while step < 600:
|
| 43 |
+
halted = V[:, MatrixMachine.HALT_IDX] > 0.5
|
| 44 |
+
first_halt[(first_halt < 0) & halted.cpu()] = step
|
| 45 |
+
if bool(halted.all()):
|
| 46 |
+
break
|
| 47 |
+
V = mm.step(V); step += 1
|
| 48 |
+
expect = torch.tensor([2 * (x if x else 256) + 1 for x in range(256)])
|
| 49 |
+
ok_halt = bool((first_halt == expect).all())
|
| 50 |
+
ok_r0 = bool((V[:, 4:12].cpu().long().sum(1) == 0).all())
|
| 51 |
+
print(f"\ncorrectness fleet of 256: every CPU halts at cycle 2n+1 for its own "
|
| 52 |
+
f"input: {'EXACT' if ok_halt else 'MISMATCH'}; all results R0==0: {ok_r0}")
|
| 53 |
+
|
| 54 |
+
# throughput fleet: 65,536 CPUs
|
| 55 |
+
B = 65536
|
| 56 |
+
V = torch.stack([state_to_vec(_mk_state(mem=mem, regs=(x & 0xFF, 1, 0, 0)))
|
| 57 |
+
for x in range(256)]).repeat(256, 1).to(dev)
|
| 58 |
+
if dev == "cuda":
|
| 59 |
+
torch.cuda.synchronize()
|
| 60 |
+
t0, steps = time.perf_counter(), 0
|
| 61 |
+
while steps < 520:
|
| 62 |
+
V = mm.step(V); steps += 1
|
| 63 |
+
if steps % 64 == 0 and bool((V[:, MatrixMachine.HALT_IDX] > 0.5).all()):
|
| 64 |
+
break
|
| 65 |
+
if dev == "cuda":
|
| 66 |
+
torch.cuda.synchronize()
|
| 67 |
+
dt = time.perf_counter() - t0
|
| 68 |
+
instr = B * steps
|
| 69 |
+
print(f"\nthroughput fleet of {B:,}: {steps} transitions in {dt:.1f}s")
|
| 70 |
+
print(f" {instr / dt / 1e6:,.1f} million instructions/s aggregate "
|
| 71 |
+
f"({instr:,} instructions retired)")
|
| 72 |
+
print(f" {instr * n_neurons / dt / 1e9:,.1f} billion threshold-neuron "
|
| 73 |
+
f"evaluations/s")
|
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_reflect -- three levels of machine in one tensor.
|
| 2 |
+
|
| 3 |
+
Level 0 is U, a fixed ternary threshold interpreter (~24k gates). Level 1 is a
|
| 4 |
+
complete SUBLEQ computer stored as data inside U's writable state. Level 2 is a
|
| 5 |
+
SUBLEQ program that rewrites its own operand bytes as it runs: a zeroing
|
| 6 |
+
instruction that walks itself across memory, erasing a block. The interpreter,
|
| 7 |
+
the machine it runs, and the program that machine executes are all the same
|
| 8 |
+
tensor; the final memory is checked bit-for-bit against a native SUBLEQ emulator.
|
| 9 |
+
|
| 10 |
+
python demos/neural_reflect_self_modifying_stack.py
|
| 11 |
+
"""
|
| 12 |
+
import os, sys, time
|
| 13 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 14 |
+
REPO = os.path.dirname(HERE)
|
| 15 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 16 |
+
import torch
|
| 17 |
+
from reflect import (Cfg, build_subleq_machine, build_net, Leveled,
|
| 18 |
+
encode_netlist, state_to_vec, subleq32_step, MEM, MEMB)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
if __name__ == "__main__":
|
| 22 |
+
dev = "cuda" if torch.cuda.is_available() else "cpu"
|
| 23 |
+
cfg = Cfg(A=14, G=296, banks=1, self_mod=False)
|
| 24 |
+
prog, lay = build_subleq_machine(cfg)
|
| 25 |
+
inet, ii, io = build_net(cfg)
|
| 26 |
+
print("neural_reflect: self-modifying code on a stored machine on U")
|
| 27 |
+
print("=" * 60)
|
| 28 |
+
print(f"level 0: interpreter U = {len(inet.gates):,} threshold gates (fixed)")
|
| 29 |
+
print(f"level 1: a SUBLEQ machine = {len(prog)} gates, stored as writable data")
|
| 30 |
+
lev = Leveled(inet, ii, io, device=dev)
|
| 31 |
+
nl = encode_netlist(cfg, prog)
|
| 32 |
+
sig0 = [0] * cfg.S
|
| 33 |
+
sig0[cfg.NET0:cfg.NET0 + len(nl)] = nl
|
| 34 |
+
base = state_to_vec(cfg, {"sig": sig0, "gp": 0, "halt": 0}).to(dev)
|
| 35 |
+
PC = lay["PC"]
|
| 36 |
+
|
| 37 |
+
# level 2: SUBLEQ (A B C: M[B]-=M[A]; if <=0 goto C). The first instruction
|
| 38 |
+
# zeroes a target cell, then two instructions increment its own A and B
|
| 39 |
+
# operand bytes, so the zeroing walks across a data block.
|
| 40 |
+
pm = [0] * 32
|
| 41 |
+
pm[0:3] = [24, 24, 3] # zero M[<target>]; these operands get rewritten
|
| 42 |
+
pm[3:6] = [19, 0, 6] # M[0] += 1 (patch own A-field: cell 0)
|
| 43 |
+
pm[6:9] = [19, 1, 9] # M[1] += 1 (patch own B-field: cell 1)
|
| 44 |
+
pm[9:12] = [20, 21, 15] # count -= 1; if <= 0 branch to epilogue
|
| 45 |
+
pm[12:15] = [18, 18, 0] # 0 -> unconditional jump to loop head
|
| 46 |
+
pm[15:18] = [18, 18, 31] # epilogue: branch to 31 = HALT
|
| 47 |
+
pm[18], pm[19], pm[20], pm[21] = 0, 255, 1, 4
|
| 48 |
+
pm[24:28] = [7, 99, 123, 200] # the block the program will erase
|
| 49 |
+
print(f"level 2: a program whose instruction 0 starts as [24,24,3] and "
|
| 50 |
+
f"rewrites its own operands")
|
| 51 |
+
|
| 52 |
+
ref_mem, ref_pc = list(pm), 0
|
| 53 |
+
while ref_pc != 31:
|
| 54 |
+
ref_mem, ref_pc = subleq32_step(ref_mem, ref_pc)
|
| 55 |
+
|
| 56 |
+
V = base.unsqueeze(0).clone()
|
| 57 |
+
for i in range(32):
|
| 58 |
+
for b in range(8):
|
| 59 |
+
V[:, MEM + b * MEMB + i] = float((pm[i] >> b) & 1)
|
| 60 |
+
|
| 61 |
+
def get_mem(Vc):
|
| 62 |
+
return sum(Vc[:, MEM + b * MEMB: MEM + b * MEMB + 32].long() << b
|
| 63 |
+
for b in range(8))[0].tolist()
|
| 64 |
+
|
| 65 |
+
t0, walk, steps, halted = time.perf_counter(), [], 0, False
|
| 66 |
+
for _ in range(26):
|
| 67 |
+
m_now = get_mem(V.cpu())
|
| 68 |
+
walk.append((m_now[0], m_now[1]))
|
| 69 |
+
for _ in range(cfg.G):
|
| 70 |
+
V = lev.step(V)
|
| 71 |
+
steps += 1
|
| 72 |
+
if int(V[0, cfg.S + cfg.GPW]) == 1:
|
| 73 |
+
halted = True
|
| 74 |
+
break
|
| 75 |
+
dt = time.perf_counter() - t0
|
| 76 |
+
got = get_mem(V.cpu())
|
| 77 |
+
|
| 78 |
+
mut = " -> ".join(f"[{a},{b}]" for a, b in
|
| 79 |
+
[walk[0]] + [w for i, w in enumerate(walk[1:], 1) if w != walk[i - 1]])
|
| 80 |
+
print(f"\nran {steps} hosted instructions in {dt:.0f}s "
|
| 81 |
+
f"({steps * cfg.G} interpreter recurrences); halted={halted}")
|
| 82 |
+
print(f" instruction 0's operand cells as it ran: {mut}")
|
| 83 |
+
print(f" target block M[24..27]: {pm[24:28]} -> {got[24:28]}")
|
| 84 |
+
print(f" final memory == native SUBLEQ reference: "
|
| 85 |
+
f"{'EXACT' if got == ref_mem else 'MISMATCH'}")
|
| 86 |
+
print("\n The program, the machine it runs on, and the machine that runs")
|
| 87 |
+
print(" that machine are all the same tensor.")
|
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_reversible -- computation with no erasure, and the counterfactual.
|
| 2 |
+
|
| 3 |
+
Every instruction of this register machine is a bijection, so the same weights
|
| 4 |
+
run backward invert the forward computation: a mixing program hashes a seed into
|
| 5 |
+
a digest, and stepping in reverse reconstructs the seed exactly, with no bit ever
|
| 6 |
+
erased (Landauer floor: zero). Because history is only consistent with the truth,
|
| 7 |
+
flipping a single bit of the digest and running backward yields a "past" that is
|
| 8 |
+
wrong in many bits: information is conserved, so a corrupted future refuses to
|
| 9 |
+
invert to the real input.
|
| 10 |
+
|
| 11 |
+
python demos/neural_reversible_counterfactual.py
|
| 12 |
+
"""
|
| 13 |
+
import os, sys
|
| 14 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 15 |
+
REPO = os.path.dirname(HERE)
|
| 16 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 17 |
+
from reversible_cpu import RCPU, _clone
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
if __name__ == "__main__":
|
| 21 |
+
# cross-coupled mixing: R0 feeds R3 and R3 feeds R0, so the avalanche spreads
|
| 22 |
+
prog = []
|
| 23 |
+
for _ in range(8):
|
| 24 |
+
prog += [("ADD", 0, 3), ("TOFF", 3, 0, 2), ("ROL", 0, 1), ("XOR", 2, 3)]
|
| 25 |
+
cpu = RCPU(prog)
|
| 26 |
+
seed = cpu.new_state(regs=[7, 13, 201, 0])
|
| 27 |
+
|
| 28 |
+
print("neural_reversible: no-erasure computing and the counterfactual")
|
| 29 |
+
print("=" * 60)
|
| 30 |
+
s = _clone(seed)
|
| 31 |
+
cpu.run(s, len(prog))
|
| 32 |
+
print(f"seed registers {seed['R']} --{len(prog)} bijective steps--> "
|
| 33 |
+
f"digest {s['R']}")
|
| 34 |
+
|
| 35 |
+
b = _clone(s)
|
| 36 |
+
cpu.run_back(b, len(prog))
|
| 37 |
+
print(f"run backward: seed recovered "
|
| 38 |
+
f"{'EXACTLY' if b['R'] == seed['R'] and b['PC'] == seed['PC'] else 'FAILED'} "
|
| 39 |
+
f"(no bit was ever erased; Landauer floor 0)")
|
| 40 |
+
|
| 41 |
+
c = _clone(s)
|
| 42 |
+
c["R"][3] ^= 4 # corrupt one bit of the digest
|
| 43 |
+
cpu.run_back(c, len(prog))
|
| 44 |
+
diff = sum(bin(x ^ y).count("1") for x, y in zip(c["R"], seed["R"]))
|
| 45 |
+
print(f"counterfactual: flip ONE digest bit, run history backward -> the "
|
| 46 |
+
f"reconstructed 'seed' is wrong in {diff}/32 register bits")
|
| 47 |
+
print(f" corrupted 'seed' = {c['R']} true seed = {seed['R']}")
|
| 48 |
+
print("\n History inverts only when it is the true history. A tampered")
|
| 49 |
+
print(" future does not lead back to the real past.")
|
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_rv32 -- pi by Machin's formula, computed on the RISC-V threshold CPU.
|
| 2 |
+
|
| 3 |
+
pi = 16*arctan(1/5) - 4*arctan(1/239) (John Machin, 1706), evaluated as an
|
| 4 |
+
integer arctangent series at scale 1e8 with rounded divisions. Every add,
|
| 5 |
+
multiply and divide runs through the ternary threshold datapath; 239^2 is
|
| 6 |
+
computed on-machine, and the result is formatted to the memory-mapped console.
|
| 7 |
+
|
| 8 |
+
python demos/neural_rv32_machin_pi.py
|
| 9 |
+
"""
|
| 10 |
+
import os, sys, time
|
| 11 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 12 |
+
REPO = os.path.dirname(HERE)
|
| 13 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 14 |
+
import torch
|
| 15 |
+
from machines import Asm, Rv32ThresholdCPU, rv_state
|
| 16 |
+
from eval_all import int_to_bits
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def native():
|
| 20 |
+
"""Integer mirror of the exact recurrence the machine will run."""
|
| 21 |
+
accA, t, sign, d, dsA = 0, 1600000000 // 5, 1, 1, []
|
| 22 |
+
while t > 0:
|
| 23 |
+
accA += sign * ((t + d // 2) // d); dsA.append(d)
|
| 24 |
+
sign, d, t = -sign, d + 2, (t + 12) // 25
|
| 25 |
+
accB, t, q, dsB, sign, d = 0, (400000000 + 119) // 239, 239 * 239, [], 1, 1
|
| 26 |
+
while t > 0:
|
| 27 |
+
accB += sign * ((t + d // 2) // d); dsB.append(d)
|
| 28 |
+
sign, d, t = -sign, d + 2, (t + q // 2) // q
|
| 29 |
+
return accA - accB, dsA, dsB
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def build(dsA, dsB):
|
| 33 |
+
a = Asm()
|
| 34 |
+
a.lui(4, 0x5F5E1) # x4 = 1,600,000,000
|
| 35 |
+
a.addi(5, 0, 25); a.addi(6, 0, 10); a.addi(8, 0, 239); a.addi(14, 0, 5)
|
| 36 |
+
a.div(11, 4, 14) # t = 16e8/5
|
| 37 |
+
a.addi(10, 0, 0) # accA
|
| 38 |
+
for k, d in enumerate(dsA):
|
| 39 |
+
a.addi(12, 0, d); a.addi(13, 11, d // 2); a.div(13, 13, 12)
|
| 40 |
+
(a.add if k % 2 == 0 else a.sub)(10, 10, 13)
|
| 41 |
+
if k + 1 < len(dsA):
|
| 42 |
+
a.addi(13, 11, 12); a.div(11, 13, 5)
|
| 43 |
+
a.lui(7, 0x17D78); a.ori(7, 7, 0x400) # x7 = 400,000,000
|
| 44 |
+
a.addi(13, 7, 119); a.div(11, 13, 8)
|
| 45 |
+
a.mul(9, 8, 8) # 239^2 computed on-machine
|
| 46 |
+
a.srli(14, 9, 1)
|
| 47 |
+
a.addi(15, 0, 0) # accB
|
| 48 |
+
for k, d in enumerate(dsB):
|
| 49 |
+
a.addi(12, 0, d); a.addi(13, 11, d // 2); a.div(13, 13, 12)
|
| 50 |
+
(a.add if k % 2 == 0 else a.sub)(15, 15, 13)
|
| 51 |
+
if k + 1 < len(dsB):
|
| 52 |
+
a.add(13, 11, 14); a.div(11, 13, 9)
|
| 53 |
+
a.sub(10, 10, 15) # x10 = pi * 1e8
|
| 54 |
+
a.addi(20, 0, 0x7F8); a.slli(20, 20, 5) # console 0xFF00
|
| 55 |
+
a.lui(21, 0x5F5E); a.ori(21, 21, 0x100) # P = 1e8
|
| 56 |
+
a.div(22, 10, 21); a.rem(10, 10, 21)
|
| 57 |
+
a.addi(22, 22, 48); a.sb(22, 20, 0) # integer digit
|
| 58 |
+
a.addi(22, 0, 46); a.sb(22, 20, 0) # '.'
|
| 59 |
+
a.div(21, 21, 6); a.addi(23, 0, 8)
|
| 60 |
+
a.label("digits")
|
| 61 |
+
a.div(22, 10, 21); a.rem(10, 10, 21)
|
| 62 |
+
a.addi(22, 22, 48); a.sb(22, 20, 0)
|
| 63 |
+
a.div(21, 21, 6); a.addi(23, 23, -1); a.bne(23, 0, "digits")
|
| 64 |
+
a.ecall()
|
| 65 |
+
return a.assemble()
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
if __name__ == "__main__":
|
| 69 |
+
pi9, dsA, dsB = native()
|
| 70 |
+
mem = build(dsA, dsB)
|
| 71 |
+
print("neural_rv32: Machin's pi on the threshold datapath")
|
| 72 |
+
print("=" * 56)
|
| 73 |
+
print("loading neural_rv32 (8.77M ternary parameters)...")
|
| 74 |
+
cpu = Rv32ThresholdCPU()
|
| 75 |
+
ts = rv_state(mem)
|
| 76 |
+
ts["_mem_bits"] = torch.tensor([int_to_bits(b, 8) for b in ts["mem"]], dtype=torch.float32)
|
| 77 |
+
t0, cyc = time.perf_counter(), 0
|
| 78 |
+
while not ts["halted"] and cyc < 400:
|
| 79 |
+
ts = cpu.step(ts); cyc += 1
|
| 80 |
+
want = f"{pi9 // 10**8}.{pi9 % 10**8:08d}"
|
| 81 |
+
print(f"console output : {ts['console']!r} ({cyc} cycles, "
|
| 82 |
+
f"{time.perf_counter()-t0:.0f}s, dual-issue pairs {cpu.pairs_issued})")
|
| 83 |
+
print(f"integer mirror : {want!r} (machine {'EXACT' if ts['console']==want else 'MISMATCH'})")
|
| 84 |
+
print(f"true pi : 3.14159265(35...) -> all printed digits "
|
| 85 |
+
f"{'correct' if ts['console']=='3.14159265' else 'NOT all correct'}")
|
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_rv32 -- neural networks running as software on the neural-net CPU.
|
| 2 |
+
|
| 3 |
+
neural_rv32's NEUR opcode computes exactly one ternary threshold neuron:
|
| 4 |
+
rd = H( popcount(rs1 & pos) - popcount(rs1 & neg) + sext5(bias) )
|
| 5 |
+
with weights in {-1,0,+1} and an integer bias. So a program of NEUR
|
| 6 |
+
instructions IS a ternary neural network -- and the CPU executing it is itself
|
| 7 |
+
a threshold network. One net here is LEARNED by gradient descent (a nonlinear
|
| 8 |
+
popcount band) and one is COMPILED by construction (8-bit parity, the function
|
| 9 |
+
that ended the single perceptron); both are then deployed as NEUR code and run
|
| 10 |
+
on the threshold datapath, with the CPU output checked against the reference net.
|
| 11 |
+
|
| 12 |
+
python demos/neural_rv32_neural_nets_via_neur.py
|
| 13 |
+
"""
|
| 14 |
+
import os, sys, time
|
| 15 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 16 |
+
REPO = os.path.dirname(HERE)
|
| 17 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 18 |
+
os.chdir(REPO)
|
| 19 |
+
import torch
|
| 20 |
+
from machines import Asm, Rv32ThresholdCPU, rv_state
|
| 21 |
+
from eval_all import int_to_bits
|
| 22 |
+
|
| 23 |
+
torch.manual_seed(0)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def ternary(w): # {-1,0,+1} forward, identity backward (STE)
|
| 27 |
+
wq = torch.where(w > 0.5, 1.0, torch.where(w < -0.5, -1.0, 0.0))
|
| 28 |
+
return w + (wq - w).detach()
|
| 29 |
+
|
| 30 |
+
def ibias(b, lo=-16, hi=15):
|
| 31 |
+
bq = b.round().clamp(lo, hi)
|
| 32 |
+
return b + (bq - b).detach()
|
| 33 |
+
|
| 34 |
+
# ---- discrete NEUR reference (exactly what the hardware computes) ----------
|
| 35 |
+
def neuron(x, pos, neg, bias):
|
| 36 |
+
return 1 if (bin(x & pos).count("1") - bin(x & neg).count("1") + bias) >= 0 else 0
|
| 37 |
+
|
| 38 |
+
def discrete_net(x, hidden, out):
|
| 39 |
+
h = 0
|
| 40 |
+
for j, (p, n, b) in enumerate(hidden):
|
| 41 |
+
h |= neuron(x, p, n, b) << j
|
| 42 |
+
p, n, b = out
|
| 43 |
+
return neuron(h, p, n, b)
|
| 44 |
+
|
| 45 |
+
# ---- train a 2-layer ternary MLP; hidden step trained with an annealed -----
|
| 46 |
+
# sigmoid so real gradients flow, then hardened to Heaviside at extraction
|
| 47 |
+
def train(target_fn, H, epochs=5000, seed=0, nbits=8):
|
| 48 |
+
torch.manual_seed(seed)
|
| 49 |
+
N = 1 << nbits
|
| 50 |
+
xs = torch.arange(N)
|
| 51 |
+
X = torch.stack([((xs >> i) & 1).float() for i in range(nbits)], 1)
|
| 52 |
+
Y = torch.tensor([float(target_fn(int(x))) for x in xs])
|
| 53 |
+
Wh = torch.nn.Parameter(torch.randn(H, nbits) * 1.5)
|
| 54 |
+
bh = torch.nn.Parameter(torch.randn(H) * 1.5)
|
| 55 |
+
Wo = torch.nn.Parameter(torch.randn(H) * 1.5)
|
| 56 |
+
bo = torch.nn.Parameter(torch.zeros(1))
|
| 57 |
+
opt = torch.optim.Adam([Wh, bh, Wo, bo], lr=0.03)
|
| 58 |
+
npos = float(Y.sum())
|
| 59 |
+
bce = torch.nn.BCEWithLogitsLoss(pos_weight=torch.tensor((N - npos) / max(npos, 1.0)))
|
| 60 |
+
for ep in range(epochs):
|
| 61 |
+
opt.zero_grad()
|
| 62 |
+
Tt = 1.0 + 11.0 * ep / epochs
|
| 63 |
+
preh = X @ ternary(Wh).T + ibias(bh)
|
| 64 |
+
h = torch.sigmoid(Tt * preh)
|
| 65 |
+
preo = h @ ternary(Wo) + ibias(bo)
|
| 66 |
+
loss = bce(4.0 * preo, Y)
|
| 67 |
+
loss.backward(); opt.step()
|
| 68 |
+
Whq = torch.where(Wh > 0.5, 1, torch.where(Wh < -0.5, -1, 0)).tolist()
|
| 69 |
+
Woq = torch.where(Wo > 0.5, 1, torch.where(Wo < -0.5, -1, 0)).tolist()
|
| 70 |
+
bhq = bh.round().clamp(-16, 15).long().tolist()
|
| 71 |
+
boq = int(bo.round().clamp(-16, 15).item())
|
| 72 |
+
def pack(wrow):
|
| 73 |
+
return (sum(1 << i for i, w in enumerate(wrow) if w > 0),
|
| 74 |
+
sum(1 << i for i, w in enumerate(wrow) if w < 0))
|
| 75 |
+
hidden = [(*pack(Whq[j]), bhq[j]) for j in range(H)]
|
| 76 |
+
out = (*pack(Woq), boq)
|
| 77 |
+
acc = sum(discrete_net(int(x), hidden, out) == int(Y[i]) for i, x in enumerate(xs)) / N
|
| 78 |
+
return hidden, out, acc
|
| 79 |
+
|
| 80 |
+
# ---- emit the network as NEUR code and run it on neural_rv32 ---------------
|
| 81 |
+
def li(a, reg, v):
|
| 82 |
+
v &= 0xFFFFFFFF
|
| 83 |
+
hi = (v + 0x800) >> 12
|
| 84 |
+
lo = v - (hi << 12)
|
| 85 |
+
a.lui(reg, hi & 0xFFFFF)
|
| 86 |
+
if lo: a.addi(reg, reg, lo)
|
| 87 |
+
|
| 88 |
+
def build_program(xval, hidden, out):
|
| 89 |
+
a = Asm()
|
| 90 |
+
li(a, 1, xval) # x1 = input byte
|
| 91 |
+
li(a, 2, 0) # x2 = packed hidden
|
| 92 |
+
for j, (p, n, b) in enumerate(hidden):
|
| 93 |
+
li(a, 4, p | (n << 8) | ((b & 0x1F) << 16)) # weight word
|
| 94 |
+
a.neur(3, 1, 4) # x3 = hidden neuron j
|
| 95 |
+
if j: a.slli(2, 2, 1)
|
| 96 |
+
a.or_(2, 2, 3) # pack MSB-first (neuron 0 -> bit H-1)
|
| 97 |
+
p, n, b = out
|
| 98 |
+
H = len(hidden)
|
| 99 |
+
def remap(mask): # match the MSB-first hidden packing
|
| 100 |
+
r = 0
|
| 101 |
+
for j in range(H):
|
| 102 |
+
if (mask >> j) & 1: r |= 1 << (H - 1 - j)
|
| 103 |
+
return r
|
| 104 |
+
li(a, 4, remap(p) | (remap(n) << 8) | ((b & 0x1F) << 16))
|
| 105 |
+
a.neur(5, 2, 4) # x5 = output
|
| 106 |
+
a.ecall()
|
| 107 |
+
return a.assemble()
|
| 108 |
+
|
| 109 |
+
def run_cpu(cpu, xval, hidden, out):
|
| 110 |
+
mem = build_program(xval, hidden, out)
|
| 111 |
+
ts = rv_state(mem)
|
| 112 |
+
ts["_mem_bits"] = torch.tensor([int_to_bits(b, 8) for b in ts["mem"]], dtype=torch.float32)
|
| 113 |
+
cyc = 0
|
| 114 |
+
while not ts["halted"] and cyc < 200:
|
| 115 |
+
ts = cpu.step(ts); cyc += 1
|
| 116 |
+
return ts["regs"][5], cyc
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def parity(x, nbits=8): return bin(x & ((1 << nbits) - 1)).count("1") & 1
|
| 120 |
+
def band(x): return int(3 <= bin(x).count("1") <= 5) # non-monotone in popcount
|
| 121 |
+
|
| 122 |
+
def learn(fn, H, seeds, epochs):
|
| 123 |
+
best, t0, used = None, time.perf_counter(), 0
|
| 124 |
+
for seed in range(seeds):
|
| 125 |
+
used = seed
|
| 126 |
+
hidden, out, acc = train(fn, H=H, seed=seed, epochs=epochs)
|
| 127 |
+
if best is None or acc > best[2]:
|
| 128 |
+
best = (hidden, out, acc)
|
| 129 |
+
if acc == 1.0:
|
| 130 |
+
break
|
| 131 |
+
hidden, out, acc = best
|
| 132 |
+
print(f" trained 8->{H}->1 ternary MLP by annealed-sigmoid SGD: {acc*100:.1f}% "
|
| 133 |
+
f"exact over all 256 inputs ({time.perf_counter()-t0:.0f}s, seed {used}); "
|
| 134 |
+
f"weights all in {{-1,0,+1}}")
|
| 135 |
+
return hidden, out, acc
|
| 136 |
+
|
| 137 |
+
def deploy_check(cpu, fn, hidden, out, sample):
|
| 138 |
+
t0, cpu_vs_net, cpu_vs_true, rows = time.perf_counter(), 0, 0, []
|
| 139 |
+
for x in sample:
|
| 140 |
+
y_cpu, _ = run_cpu(cpu, x, hidden, out)
|
| 141 |
+
cpu_vs_net += (y_cpu == discrete_net(x, hidden, out))
|
| 142 |
+
cpu_vs_true += (y_cpu == fn(x))
|
| 143 |
+
rows.append((x, y_cpu, fn(x)))
|
| 144 |
+
print(f" NEUR execution on neural_rv32: CPU == reference net on "
|
| 145 |
+
f"{cpu_vs_net}/{len(sample)} (deployment exact); CPU == truth on "
|
| 146 |
+
f"{cpu_vs_true}/{len(sample)} ({time.perf_counter()-t0:.0f}s)")
|
| 147 |
+
return rows
|
| 148 |
+
|
| 149 |
+
def compile_parity(nbits=8): # exact thermometer construction
|
| 150 |
+
mask = (1 << nbits) - 1
|
| 151 |
+
hidden = [(mask, 0, -(k + 1)) for k in range(nbits)] # h_k = [popcount>=k+1]
|
| 152 |
+
pos = sum(1 << j for j in range(nbits) if j % 2 == 0) # odd thresholds
|
| 153 |
+
neg = sum(1 << j for j in range(nbits) if j % 2 == 1) # even thresholds
|
| 154 |
+
return hidden, (pos, neg, -1)
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
if __name__ == "__main__":
|
| 158 |
+
print("=" * 72)
|
| 159 |
+
print(" Neural networks running as software on the CPU that IS a neural net")
|
| 160 |
+
print("=" * 72)
|
| 161 |
+
cpu = Rv32ThresholdCPU()
|
| 162 |
+
|
| 163 |
+
print("\n[1] LEARNED by gradient descent -- popcount BAND: fire iff 3<=popcount<=5")
|
| 164 |
+
print(" (non-monotone, so no single threshold can do it; genuinely nonlinear)")
|
| 165 |
+
hidden, out, acc = learn(band, H=8, seeds=12, epochs=5000)
|
| 166 |
+
deploy_check(cpu, band, hidden, out,
|
| 167 |
+
[0, 1, 3, 7, 15, 31, 63, 127, 255, 0b10110, 0b1111000, 0b11011011])
|
| 168 |
+
line = "".join(f"{k}:{discrete_net((1 << k) - 1, hidden, out)} " for k in range(9))
|
| 169 |
+
print(f" net output vs popcount k: {line.strip()} (true band: k in 3,4,5)")
|
| 170 |
+
|
| 171 |
+
print("\n[2] COMPILED by construction -- 8-bit PARITY (XOR), the non-separable")
|
| 172 |
+
print(" function Minsky & Papert used to end the single perceptron")
|
| 173 |
+
hidden, out = compile_parity(8)
|
| 174 |
+
acc = sum(discrete_net(x, hidden, out) == parity(x) for x in range(256)) / 256
|
| 175 |
+
print(f" thermometer net (8 hidden 'popcount>=k' + alternating output), all "
|
| 176 |
+
f"weights in {{-1,0,+1}}: {acc*100:.0f}% exact over all 256 bytes")
|
| 177 |
+
rows = deploy_check(cpu, parity, hidden, out,
|
| 178 |
+
[0, 1, 3, 7, 15, 31, 85, 127, 128, 170, 200, 254, 255])
|
| 179 |
+
for x, yc, yt in rows:
|
| 180 |
+
print(f" parity({x:3d}) = {yc} (true {yt}) [{'ok' if yc==yt else 'x'}]")
|
| 181 |
+
|
| 182 |
+
print("\n One network learned by SGD, one compiled by hand, both executed")
|
| 183 |
+
print(" instruction by instruction on a CPU whose every gate is a threshold")
|
| 184 |
+
print(" neuron. The machine running the models belongs to the models' own class.")
|
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_subleq8io -- von Neumann's universal constructor, to the byte.
|
| 2 |
+
|
| 3 |
+
The one-instruction machine (194 gates) runs a 21-instruction SUBLEQ program
|
| 4 |
+
that reads a description of any machine in the family off its tape and emits
|
| 5 |
+
that machine's safetensors file one byte at a time. Here it fabricates
|
| 6 |
+
neural_tile.safetensors on the shipped gate netlist, byte-for-byte sha-identical
|
| 7 |
+
to the target, and the fabricated file is then booted: tiles reconstructed from
|
| 8 |
+
its own bytes regrow the binary counter. A computer made of threshold gates
|
| 9 |
+
prints a working computer, and the product runs.
|
| 10 |
+
|
| 11 |
+
python demos/neural_subleq8io_universal_constructor.py
|
| 12 |
+
"""
|
| 13 |
+
import os, sys, time, json, hashlib
|
| 14 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 15 |
+
REPO = os.path.dirname(HERE)
|
| 16 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 17 |
+
os.chdir(REPO)
|
| 18 |
+
from constructor8 import describe, ref_construct, GateHost
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
if __name__ == "__main__":
|
| 22 |
+
target_path = os.path.join(REPO, "variants", "neural_tile.safetensors")
|
| 23 |
+
data = open(target_path, "rb").read()
|
| 24 |
+
tape = describe(data)
|
| 25 |
+
print("neural_subleq8io: the universal constructor fabricates a sibling")
|
| 26 |
+
print("=" * 60)
|
| 27 |
+
print(f"recipe compiled: {len(data)} B machine -> {len(tape)} B tape")
|
| 28 |
+
|
| 29 |
+
out_ref, n_ref = ref_construct(tape)
|
| 30 |
+
assert out_ref == data
|
| 31 |
+
print(f"reference: constructor needs {n_ref:,} instructions")
|
| 32 |
+
|
| 33 |
+
host = GateHost()
|
| 34 |
+
t0 = time.perf_counter()
|
| 35 |
+
out, n, _ = host.run(tape, max_steps=n_ref + 100)
|
| 36 |
+
dt = time.perf_counter() - t0
|
| 37 |
+
sha_out = hashlib.sha256(out).hexdigest()[:16]
|
| 38 |
+
sha_tgt = hashlib.sha256(data).hexdigest()[:16]
|
| 39 |
+
print(f"gate netlist: {n:,} instructions in {dt:.0f}s ({n / dt:.0f} instr/s), "
|
| 40 |
+
f"{len(out)} bytes emitted")
|
| 41 |
+
print(f" sha256 fabricated={sha_out} shipped={sha_tgt} "
|
| 42 |
+
f"{'BYTE-IDENTICAL' if out == data else 'MISMATCH'}; "
|
| 43 |
+
f"instruction count == reference: {n == n_ref}")
|
| 44 |
+
|
| 45 |
+
# boot the fabricated machine: regrow its counter from its own bytes
|
| 46 |
+
fab = os.path.join(os.environ.get("TEMP", "."), "fabricated_neural_tile.safetensors")
|
| 47 |
+
open(fab, "wb").write(out)
|
| 48 |
+
from safetensors.torch import load_file
|
| 49 |
+
from safetensors import safe_open
|
| 50 |
+
import tile as T
|
| 51 |
+
|
| 52 |
+
t = load_file(fab)
|
| 53 |
+
with safe_open(fab, framework="pt") as f:
|
| 54 |
+
m = f.metadata()
|
| 55 |
+
gl = json.loads(m["glues"])
|
| 56 |
+
strg = {gl[i]: int(v) for i, v in enumerate(t["glue_strength"].tolist())}
|
| 57 |
+
tiles = []
|
| 58 |
+
for row, name in zip(t["tile_glues"].tolist(), json.loads(m["tile_names"])):
|
| 59 |
+
sides = [gl[i] if i >= 0 else "" for i in row]
|
| 60 |
+
tiles.append(T.Tile(N=sides[0], E=sides[1], S=sides[2], W=sides[3], name=name))
|
| 61 |
+
NB = 8
|
| 62 |
+
rows = (1 << NB) - 1
|
| 63 |
+
A, det = T.grow(tiles, T.counter_seed(NB), int(m["tau"]), strg, (0, 0, NB, rows))
|
| 64 |
+
bad = filled = 0
|
| 65 |
+
for y in range(1, rows + 1):
|
| 66 |
+
cells = [A.get((x, y)) for x in range(NB)]
|
| 67 |
+
if any(cc is None for cc in cells):
|
| 68 |
+
continue
|
| 69 |
+
filled += 1
|
| 70 |
+
v = sum((1 if cc.N == "b1" else 0) << (NB - 1 - x) for x, cc in enumerate(cells))
|
| 71 |
+
bad += (v != y)
|
| 72 |
+
print(f"boot: the fabricated file grew its counter, {filled} rows, "
|
| 73 |
+
f"row y encodes y: {'EXACT' if bad == 0 and filled == rows else f'{bad} bad'}")
|
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""neural_tile -- self-assembly grows Pascal's triangle mod 2 (Lucas' theorem).
|
| 2 |
+
|
| 3 |
+
The program is a set of square tiles whose binding rule is a threshold gate: a
|
| 4 |
+
tile attaches at a site when the summed strength of its matching glues meets the
|
| 5 |
+
temperature. With the XOR rule tile set, the crystal that grows is exactly the
|
| 6 |
+
Sierpinski triangle -- cell (x,y) is filled iff the binomial C(x+y, x) is odd,
|
| 7 |
+
which by Lucas' theorem is iff x AND y == 0. Every filled cell is verified
|
| 8 |
+
against that arithmetic. Computation as crystal growth.
|
| 9 |
+
|
| 10 |
+
python demos/neural_tile_pascal_lucas.py
|
| 11 |
+
"""
|
| 12 |
+
import os, sys, time
|
| 13 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 14 |
+
REPO = os.path.dirname(HERE)
|
| 15 |
+
sys.path.insert(0, os.path.join(REPO, "src"))
|
| 16 |
+
import tile as T
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
if __name__ == "__main__":
|
| 20 |
+
N = 100
|
| 21 |
+
print("neural_tile: Pascal mod 2 by threshold-gated self-assembly")
|
| 22 |
+
print("=" * 60)
|
| 23 |
+
t0 = time.perf_counter()
|
| 24 |
+
ts = T.rule2_tileset(lambda w, s: w ^ s)
|
| 25 |
+
seed = T._row_col_seed([1] * (N + 1), [1] * (N + 1))
|
| 26 |
+
A, det = T.grow(ts, seed, tau=2, strength={}, bounds=(0, 0, N, N), max_tiles=200000)
|
| 27 |
+
dt = time.perf_counter() - t0
|
| 28 |
+
|
| 29 |
+
interior = [(x, y) for x in range(1, N + 1) for y in range(1, N + 1)]
|
| 30 |
+
grown = sum(1 for p in interior if p in A)
|
| 31 |
+
bad = 0
|
| 32 |
+
for (x, y) in interior:
|
| 33 |
+
v = 1 if A[(x, y)].N == "v1" else 0
|
| 34 |
+
lucas = 1 if (x & y) == 0 else 0 # C(x+y,x) odd <=> no carry in x+y
|
| 35 |
+
bad += (v != lucas)
|
| 36 |
+
print(f"grew {grown} rule tiles in {dt:.1f}s (directed={det}, "
|
| 37 |
+
f"{2 * N} anti-diagonals deep)")
|
| 38 |
+
print(f"tile(x,y) == [C(x+y,x) is odd] for all {len(interior)} cells: "
|
| 39 |
+
f"{'EXACT' if bad == 0 else f'{bad} MISMATCHES'}")
|
| 40 |
+
print("\ncorner of the assembly (30x30, '#' = odd binomial):")
|
| 41 |
+
for y in range(30, 0, -1):
|
| 42 |
+
print(" " + "".join("#" if A[(x, y)].N == "v1" else "." for x in range(1, 31)))
|