CharlesCNorton commited on
Commit
28868a6
·
1 Parent(s): db536d3

Add neural_matrix8 and neural_subleq8io (universal constructor); move dev scripts to tools/

Browse files
README.md CHANGED
@@ -5,6 +5,9 @@ tags:
5
  - neuromorphic
6
  - computer-architecture
7
  - turing-complete
 
 
 
8
  - loihi
9
  - truenorth
10
  - akida
@@ -32,14 +35,24 @@ variants/neural_computer{8,16,32}_registers.safetensors 16 B memory
32
  variants/neural_alu{8,16,32}.safetensors pure ALU, no memory
33
  variants/neural_subleq8.safetensors one-instruction machine (SUBLEQ)
34
  variants/neural_rv32.safetensors RV32IM + F-subset RISC-V processor
 
 
35
  ```
36
 
37
- Two further machines mark the endpoints of the family, each detailed in
38
- its own section below: `neural_subleq8`, a Turing-complete one-instruction
39
- computer whose entire control flow is a single threshold neuron; and
40
- `neural_rv32`, a RISC-V processor (RV32IM + an F subset) that runs
41
- stock-compiler C, with dual-issue execution, memory-mapped I/O, and a
42
- self-referential NEUR opcode.
 
 
 
 
 
 
 
 
43
 
44
  ---
45
 
@@ -77,19 +90,19 @@ python eval_all.py --cpu-program variants/ # also run an assemble
77
  For an interactive walkthrough that exercises Boolean gates, the 8-bit ALU, mod-5 divisibility, and a CPU loop end-to-end:
78
 
79
  ```bash
80
- python play.py # 1 KB demo, runs in seconds
81
- python play.py --model neural_computer.safetensors # 64 KB, slower
82
  ```
83
 
84
  For end-to-end CPU validation (Fibonacci, sum 1..N, bubble sort, self-modifying JMP, all eight conditional jumps, CALL stack semantics, MUL cross-checked against repeated ADD, DIV cross-checked against repeated SUB, a bitwise AND/OR/XOR/SHL/SHR chain, and the architectural flag policy):
85
 
86
  ```bash
87
- python test_cpu.py # default: 1 KB, ~2 s
88
- python test_cpu.py --model neural_computer.safetensors # 64 KB canonical, ~2 min
89
- python test_cpu.py --only fib,sum_n # subset of suite
90
  ```
91
 
92
- Each program is assembled by a small Python assembler (`cpu_programs.py`) and run through the threshold-gated CPU; the driver verifies expected memory contents at HALT.
93
 
94
  ---
95
 
@@ -290,7 +303,7 @@ python build.py --bits 32 -a 6 --apply all # neural_computer32_addr6.saf
290
  To regenerate every named variant in one pass:
291
 
292
  ```bash
293
- python build_all.py
294
  ```
295
 
296
  This populates `variants/` with all 18 builds, quantizes each one to the smallest signed integer dtype that exactly represents its weights (~4× reduction in tensor data, with file size dominated by the safetensors header on the smaller profiles), verifies the strictly ternary weight invariant (`--ternary --strict`, so a build with any non-ternary weight fails loudly), stamps the `weight_quantization` metadata field, and runs `eval.py` on each as a sanity check.
@@ -334,7 +347,7 @@ Every weight and bias tensor in the canonical model fits in `int8`. The eval pip
334
 
335
  The 8-bit arithmetic and ALU tests use strategic sampling rather than the full 65,536-case sweep because exhaustive coverage at 8-bit is feasible but not necessary given that the circuits are constructed gate-by-gate. The 16-bit and 32-bit arithmetic tests sample edge cases only; full exhaustive coverage at those widths is infeasible without specialized hardware.
336
 
337
- `eval_all.py` runs the unified suite. Exit code is the number of failing variants (0 means all pass). **Testing is evaluation, not rebuilding**: `python eval_all.py variants/` scores all 18 fitness variants against the shipped weights in about two minutes (~6 s each, the composed float netlists evaluated in `NetlistEvaluator`'s leveled mode) and cleanly skips the two standalone machines. Rebuilding the models (`build_all.py`, ~50 min for all 18) is a separate step, needed only when the circuit constructions in `build.py` change routine verification never rebuilds. The batched evaluator is population-safe: every chained intermediate (carry, borrow, mux select) is computed per population slot, so `prune_weights.py`'s parallel fitness screens are exact rather than slot-0 approximations.
338
 
339
  ---
340
 
@@ -381,6 +394,110 @@ The composed circuits evaluate in a leveled mode (`NetlistEvaluator`, one padded
381
 
382
  ---
383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  ## Threshold logic
385
 
386
  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.
@@ -553,18 +670,22 @@ Loss components: BCE on output bits, BCE on extracted A and B bits (2× weight),
553
 
554
  ```
555
  neural_computer.safetensors canonical model (32-bit, 64 KB, ~8.61M params)
556
- variants/ 18 prebuilt configurations + neural_subleq8 + neural_rv32
 
557
  build.py generator (one safetensors per invocation; also `subleq`, `rv32`)
558
- build_all.py builds, quantizes (--ternary --strict), and verifies every profile
559
  quantize.py min integer dtypes + ternary verification/repair
560
- eval.py gate-level fitness suite, NetlistEvaluator, 64 KB reference CPU
561
  eval_all.py variant-agnostic harness + manifest-sized threshold CPU
562
- cpu_programs.py assembler + ten-program suite for CPU-level validation
563
- test_cpu.py runs the program suite against a chosen variant
564
  machines.py neural_subleq8 + neural_rv32 runtimes, references, assemblers,
565
- RV32 object loader, and both test suites (subleq / rv32 / rv32-c)
566
- play.py interactive demo
567
- prune_weights.py GPU-batched weight reduction with conflict resolution
 
 
 
 
 
 
568
  llm_integration/ SmolLM2 extractor + circuit wrapper + training code
569
  ├── circuits.py FrozenThresholdCircuits (loads safetensors, exposes
570
  │ add_8bit / sub_8bit / mul_8bit / compare_*)
 
5
  - neuromorphic
6
  - computer-architecture
7
  - turing-complete
8
+ - recurrent-neural-network
9
+ - in-memory-computing
10
+ - self-replication
11
  - loihi
12
  - truenorth
13
  - akida
 
35
  variants/neural_alu{8,16,32}.safetensors pure ALU, no memory
36
  variants/neural_subleq8.safetensors one-instruction machine (SUBLEQ)
37
  variants/neural_rv32.safetensors RV32IM + F-subset RISC-V processor
38
+ variants/neural_matrix8.safetensors the CPU as one recurrent ternary matrix stack
39
+ variants/neural_subleq8io.safetensors SUBLEQ host for the universal constructor
40
  ```
41
 
42
+ Four further machines are detailed in their own sections below, and together
43
+ they carry the family from the smallest possible processor to two results
44
+ about what a threshold network can be. `neural_subleq8` is a Turing-complete
45
+ one-instruction computer whose entire control flow is a single threshold
46
+ neuron. `neural_rv32` is a RISC-V processor (RV32IM plus an F subset) that
47
+ runs stock-compiler C, with dual-issue execution, memory-mapped I/O, and a
48
+ self-referential NEUR opcode. `neural_matrix8` drops the gate graph entirely:
49
+ the whole processor is compiled into a fixed stack of ternary weight matrices
50
+ with a Heaviside step between them, so one clock cycle is one matrix-vector
51
+ product followed by thresholding, exactly what a resistive or photonic
52
+ crossbar computes. And `neural_subleq8io` hosts a universal constructor: a
53
+ program that reads a description of any machine in the family and prints that
54
+ machine's weight file byte for byte, self-reproduction being the case where
55
+ the description is its own.
56
 
57
  ---
58
 
 
90
  For an interactive walkthrough that exercises Boolean gates, the 8-bit ALU, mod-5 divisibility, and a CPU loop end-to-end:
91
 
92
  ```bash
93
+ python tools/play.py # 1 KB demo, runs in seconds
94
+ python tools/play.py --model neural_computer.safetensors # 64 KB, slower
95
  ```
96
 
97
  For end-to-end CPU validation (Fibonacci, sum 1..N, bubble sort, self-modifying JMP, all eight conditional jumps, CALL stack semantics, MUL cross-checked against repeated ADD, DIV cross-checked against repeated SUB, a bitwise AND/OR/XOR/SHL/SHR chain, and the architectural flag policy):
98
 
99
  ```bash
100
+ python tools/test_cpu.py # default: 1 KB, ~2 s
101
+ python tools/test_cpu.py --model neural_computer.safetensors # 64 KB canonical, ~2 min
102
+ python tools/test_cpu.py --only fib,sum_n # subset of suite
103
  ```
104
 
105
+ Each program is assembled by a small Python assembler (`tools/cpu_programs.py`) and run through the threshold-gated CPU; the driver verifies expected memory contents at HALT.
106
 
107
  ---
108
 
 
303
  To regenerate every named variant in one pass:
304
 
305
  ```bash
306
+ python tools/build_all.py
307
  ```
308
 
309
  This populates `variants/` with all 18 builds, quantizes each one to the smallest signed integer dtype that exactly represents its weights (~4× reduction in tensor data, with file size dominated by the safetensors header on the smaller profiles), verifies the strictly ternary weight invariant (`--ternary --strict`, so a build with any non-ternary weight fails loudly), stamps the `weight_quantization` metadata field, and runs `eval.py` on each as a sanity check.
 
347
 
348
  The 8-bit arithmetic and ALU tests use strategic sampling rather than the full 65,536-case sweep because exhaustive coverage at 8-bit is feasible but not necessary given that the circuits are constructed gate-by-gate. The 16-bit and 32-bit arithmetic tests sample edge cases only; full exhaustive coverage at those widths is infeasible without specialized hardware.
349
 
350
+ `eval_all.py` runs the unified suite. Exit code is the number of failing variants (0 means all pass). **Testing is evaluation, not rebuilding**: `python eval_all.py variants/` scores all 18 fitness variants against the shipped weights in about two minutes (~6 s each, the composed float netlists evaluated in `NetlistEvaluator`'s leveled mode) and cleanly skips the four standalone machines. Rebuilding the models (`tools/build_all.py`, ~50 min for all 18) is a separate step, needed only when the circuit constructions in `build.py` change; routine verification never rebuilds. The batched evaluator is population-safe: every chained intermediate (carry, borrow, mux select) is computed per population slot, so `tools/prune_weights.py`'s parallel fitness screens are exact rather than slot-0 approximations.
351
 
352
  ---
353
 
 
394
 
395
  ---
396
 
397
+ ## neural_matrix8 — the processor as one recurrent threshold network
398
+
399
+ Every machine above is a graph of individually-named gates. `neural_matrix8`
400
+ is the same processor with the graph dissolved: the verified gate wiring of
401
+ the registers-profile CPU (8-bit data, 4-bit addresses, 16 B of memory) is
402
+ compiled into a fixed sequence of ternary weight matrices `W1..Wk` with a
403
+ Heaviside step between them, acting on a single state vector that holds the
404
+ program counter, all four registers, the flags, the stack pointer, the halt
405
+ bit, and every memory bit:
406
+
407
+ ```
408
+ [ PC[4] | R0[8] R1[8] R2[8] R3[8] | Z N C V | SP[4] | HALT | MEM[16][8] ] = 173 bits
409
+ ```
410
+
411
+ One instruction is one pass through the stack, `s' = H(Wk · … H(W1·s + b1) …
412
+ + bk)`; the machine is that stack iterated until the halt bit sets, so the
413
+ whole processor is a single recurrent linear-threshold network. The
414
+ transition is total: a halted state is a fixed point (every architectural
415
+ write is gated by NOT halt), so iterating past HALT is harmless.
416
+
417
+ - **Ternary and small-integer, end to end.** The step circuit is assembled
418
+ from the family's verified cells (two-layer OR/NAND XOR, ripple-carry full
419
+ adders, bit-cascade comparators, restoring-division stages, 2:1 muxes,
420
+ one-gate address-decode rows), then levelized with identity pass-throughs
421
+ carrying live signals forward. It compiles to 108 matrices whose every
422
+ entry is in `{-1, 0, 1}` with small integer biases (~9.1 MB on disk).
423
+ - **Equality is machine-checked bit for bit,** not sampled: all 65,536 `(a,
424
+ b)` operand pairs for each of the ten ALU opcodes (ADD SUB AND OR XOR SHL
425
+ SHR MUL DIV CMP) batched through the matrices against an integer reference;
426
+ the full Jcc × flag, JMP, CALL, and LOAD/STORE control space; 2,000 uniformly
427
+ random full states plus 500 halted-state fixed points; and per-instruction
428
+ full-state lockstep against the gate-graph `GenericThresholdCPU` walking the
429
+ shipped `neural_computer8_registers` weights.
430
+ - **Analog realization.** Pre-activations are integers; a firing gate sits at
431
+ `>= 0` and a non-firing gate at `<= -1`, so a comparator threshold at `-0.5`
432
+ gives every gate a guaranteed noise margin of exactly 0.5 (measured minimum
433
+ over random states: 0.500). Any total analog error below 0.5 per
434
+ pre-activation therefore reproduces the digital circuit bit for bit. Each
435
+ matrix maps to a crossbar with conductances drawn from `{-1, 0, +1}` and one
436
+ clock cycle is one analog matrix-vector product followed by thresholding;
437
+ the suite injects Gaussian read noise (bit-exact through σ ≈ 0.10, breaking
438
+ down near σ = 0.15 exactly where the error model predicts) and static
439
+ conductance mismatch (bit-exact through σ_G = 0.10).
440
+
441
+ ```bash
442
+ python matrix8.py build # compile + save variants/neural_matrix8.safetensors
443
+ python matrix8.py verify # exhaustive equality vs the gate graph and integer reference
444
+ python matrix8.py analog # margin measurement + read-noise + conductance-mismatch sweeps
445
+ ```
446
+
447
+ Because the transition is a fixed matrix stack proven equal to the gate-graph
448
+ processor, the ISA semantics carry over unchanged, and the digital circuit and
449
+ its analog crossbar realization coincide within the device's quantization and
450
+ noise margins. The processor is no longer *described by* a neural network; it
451
+ *is* one, iterated.
452
+
453
+ ---
454
+
455
+ ## neural_subleq8io and the universal constructor — a machine that prints itself
456
+
457
+ `neural_subleq8io` is the one-instruction machine extended with three
458
+ memory-mapped device cells (a tape-input byte, an input strobe, and an output
459
+ byte). The device logic lives in the runtime, exactly as `neural_rv32`'s
460
+ console does; the processor's gates are unchanged, 194 of them. On this host
461
+ runs the **universal constructor**: a 21-instruction SUBLEQ program that reads
462
+ a *construction recipe* off the tape and emits a safetensors file one byte at
463
+ a time.
464
+
465
+ `describe(file_bytes)` compiles any file in the family into a recipe; running
466
+ the constructor on that recipe emits the file back byte for byte. The
467
+ constructor is therefore universal, and the description selects what is built.
468
+ Self-reproduction is the single case where the description handed to the
469
+ constructor is that of the running machine: the program's output stream is
470
+ then the byte-for-byte safetensors file of the host itself.
471
+
472
+ The equality is machine-checked rather than observed on one run:
473
+
474
+ - the recipe codec round-trips every file in the family (all 23 shipped
475
+ `.safetensors`, 551 MB, byte-identical and sha-verified);
476
+ - the constructor program is executed on three independently-verified
477
+ backends — a pure-integer reference, the gate-graph `SubleqThresholdCPU`
478
+ walking the shipped netlist, and the host itself compiled to recurrent
479
+ ternary matrices by `matrix8`'s compiler — and they agree cycle for cycle;
480
+ - **full self-reproduction runs entirely on the threshold matrices:** the host
481
+ compiles to 34 ternary layers, and generation 1 emits all 95,306 bytes of
482
+ its own weight file in 359,743 recurrences, each output byte asserted
483
+ against the target as it streams, ending on an sha match and an instruction
484
+ count identical to the reference;
485
+ - the offspring file then boots as generation 2 and reconstructs itself,
486
+ closing the loop across two generations.
487
+
488
+ ```bash
489
+ python constructor8.py build # emit variants/neural_subleq8io.safetensors
490
+ python constructor8.py verify # host soundness + codec round-trip + constructor on all 3 backends
491
+ python constructor8.py self # full self-reproduction on the threshold matrices, then gen-2 boot
492
+ ```
493
+
494
+ A program running on the processor emits the exact tensor file that defines
495
+ that processor, and builds any other machine in the family from a description
496
+ of it: von Neumann's universal constructor, realized as ternary threshold
497
+ 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
 
671
  ```
672
  neural_computer.safetensors canonical model (32-bit, 64 KB, ~8.61M params)
673
+ variants/ 18 fitness variants + 4 standalone machines
674
+ (neural_subleq8, neural_rv32, neural_matrix8, neural_subleq8io)
675
  build.py generator (one safetensors per invocation; also `subleq`, `rv32`)
 
676
  quantize.py min integer dtypes + ternary verification/repair
677
+ eval.py gate-level fitness suite, NetlistEvaluator, float oracles, reference CPU
678
  eval_all.py variant-agnostic harness + manifest-sized threshold CPU
 
 
679
  machines.py neural_subleq8 + neural_rv32 runtimes, references, assemblers,
680
+ RV32 object loader, and the test suites (subleq / rv32 / rv32-c)
681
+ matrix8.py compiles the CPU to a recurrent ternary matrix stack; the
682
+ exhaustive equality suite and the analog crossbar simulation
683
+ constructor8.py the neural_subleq8io host, the recipe codec, and the universal
684
+ constructor / self-reproduction suite
685
+ tools/ build_all.py (build + quantize + verify every profile),
686
+ cpu_programs.py (assembler + CPU program suite), test_cpu.py
687
+ (program suite vs a variant), play.py (interactive demo),
688
+ prune_weights.py (GPU-batched weight reduction)
689
  llm_integration/ SmolLM2 extractor + circuit wrapper + training code
690
  ├── circuits.py FrozenThresholdCircuits (loads safetensors, exposes
691
  │ add_8bit / sub_8bit / mul_8bit / compare_*)
constructor8.py ADDED
@@ -0,0 +1,646 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """neural_subleq8io + the universal constructor.
2
+
3
+ The host is the family's one-instruction machine extended with three
4
+ memory-mapped device cells (all device logic lives in the runtimes, exactly
5
+ like neural_rv32's console at 0xFF00; the processor's gates are unchanged):
6
+
7
+ 0xFD IN_DATA reads as the current tape byte (device-refreshed)
8
+ 0xFC IN_STROBE any write advances the tape head (device clears to 0)
9
+ 0xFE OUT any write emits the written byte (device clears to 0)
10
+
11
+ A 21-instruction SUBLEQ program — the constructor — interprets a construction
12
+ recipe streamed on the tape:
13
+
14
+ tag 0 END
15
+ tag 1..127 literal record: the next `tag` tape bytes, each stored
16
+ negated mod 256, are emitted one per SUBLEQ instruction
17
+ (M[0xFE] = 0 - M[0xFD] recovers the byte)
18
+ tag 129..255 repeat record: the next tape byte (negated value) is
19
+ emitted 256-tag times without strobing (tag 128 unused)
20
+
21
+ `describe(file_bytes)` compiles any safetensors file in the family into a
22
+ recipe; the constructor run on that recipe emits the file byte for byte, so
23
+ the constructor is universal and the description selects what is built.
24
+ Self-reproduction is the case where the description handed to the constructor
25
+ is that of the running machine: the program's output stream is the
26
+ byte-for-byte safetensors file of the host itself.
27
+
28
+ The equality is machine-checked rather than observed on a single run:
29
+ - the recipe codec round-trips every family file (byte equality + sha256);
30
+ - the constructor program is executed on three proven-equal backends
31
+ (pure-integer reference, the gate-graph SubleqThresholdCPU walking the
32
+ shipped netlist, and the host compiled to recurrent ternary matrices with
33
+ matrix8's compiler) with cycle-lockstep spot checks between them;
34
+ - the full self-reproduction runs at threshold fidelity on the matrix
35
+ backend, and the emitted stream must equal the host's file exactly;
36
+ - the offspring file then boots as generation 2 and reproduces again.
37
+
38
+ Usage:
39
+ python constructor8.py build # emit variants/neural_subleq8io.safetensors
40
+ python constructor8.py verify # host soundness + codec + constructor runs
41
+ python constructor8.py self # full self-reproduction at threshold fidelity
42
+ python constructor8.py all
43
+ """
44
+
45
+ from __future__ import annotations
46
+
47
+ import argparse
48
+ import hashlib
49
+ import json
50
+ import os
51
+ import sys
52
+ import time
53
+ from typing import Dict, List, Optional, Tuple
54
+
55
+ import torch
56
+ from safetensors import safe_open
57
+ from safetensors.torch import save_file
58
+
59
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
60
+
61
+ REPO = os.path.dirname(os.path.abspath(__file__))
62
+ MODEL_PATH = os.path.join(REPO, "variants", "neural_subleq8io.safetensors")
63
+
64
+ from matrix8 import Net, compile_net # machine-1 compiler, reused verbatim
65
+
66
+ IN_DATA, IN_STROBE, OUT = 0xFD, 0xFC, 0xFE
67
+ HALT_PC = 0xFF
68
+
69
+
70
+ # =============================================================================
71
+ # Build the host file (family per-gate style, .inputs wiring, ternary strict)
72
+ # =============================================================================
73
+
74
+ def build_host(save: bool = True) -> str:
75
+ import build as B
76
+ from quantize import quantize_tensors
77
+
78
+ tensors: Dict[str, torch.Tensor] = {}
79
+ B.add_decoder(tensors, 8, 256)
80
+ B.add_memory_read_mux(tensors, 256)
81
+ B.add_memory_write_cells(tensors, 256)
82
+ B.add_subleq_core(tensors)
83
+ tensors["manifest.data_bits"] = torch.tensor([8.0])
84
+ tensors["manifest.addr_bits"] = torch.tensor([8.0])
85
+ tensors["manifest.memory_bytes"] = torch.tensor([256.0])
86
+ tensors["manifest.pc_width"] = torch.tensor([8.0])
87
+ tensors["manifest.version"] = torch.tensor([4.0])
88
+ tensors, reg, stats = B.build_inputs(tensors)
89
+ tensors, counts, _, _ = quantize_tensors(tensors, ternary=False)
90
+ for k, t in tensors.items():
91
+ if k.endswith(".weight") and not k.startswith("manifest."):
92
+ tf = t.float() if t.dtype.is_floating_point else t.to(torch.float32)
93
+ assert (tf.abs() <= 1).all(), f"non-ternary weight {k}"
94
+ meta = {
95
+ "machine": "subleq8io",
96
+ "weight_quantization": "ternary",
97
+ "signal_registry": reg.to_metadata(),
98
+ "io": json.dumps({"in_data": IN_DATA, "in_strobe": IN_STROBE,
99
+ "out": OUT, "halt_pc": HALT_PC}),
100
+ }
101
+ if save:
102
+ save_file(tensors, MODEL_PATH, metadata=meta)
103
+ sz = os.path.getsize(MODEL_PATH)
104
+ print(f" built {MODEL_PATH}: {stats['added']} wired gates, "
105
+ f"{len(tensors)} tensors, {sz:,} bytes")
106
+ return MODEL_PATH
107
+
108
+
109
+ # =============================================================================
110
+ # Recipe codec (the construction-description language)
111
+ # =============================================================================
112
+
113
+ def describe(data: bytes) -> bytes:
114
+ """Compile raw bytes into a construction recipe for the constructor."""
115
+ tape = bytearray()
116
+ i, n = 0, len(data)
117
+ while i < n:
118
+ j = i
119
+ while j < n and data[j] == data[i] and j - i < 127:
120
+ j += 1
121
+ if j - i >= 4:
122
+ tape.append(256 - (j - i)) # repeat tag 129..252
123
+ tape.append((256 - data[i]) % 256) # value, negated
124
+ i = j
125
+ continue
126
+ k = i
127
+ while k < n and k - i < 127: # literal until a run starts
128
+ m = k
129
+ while m < n and data[m] == data[k] and m - k < 4:
130
+ m += 1
131
+ if m - k >= 4:
132
+ break
133
+ k += 1
134
+ k = max(k, i + 1)
135
+ tape.append(k - i) # literal tag 1..127
136
+ tape.extend((256 - x) % 256 for x in data[i:k])
137
+ i = k
138
+ tape.append(0) # END
139
+ return bytes(tape)
140
+
141
+
142
+ def decode(tape: bytes) -> bytes:
143
+ """Pure-Python reference semantics of the recipe language."""
144
+ out = bytearray()
145
+ i = 0
146
+ while True:
147
+ t = tape[i]
148
+ i += 1
149
+ if t == 0:
150
+ return bytes(out)
151
+ if t <= 127:
152
+ for _ in range(t):
153
+ out.append((256 - tape[i]) % 256)
154
+ i += 1
155
+ else:
156
+ out.extend([(256 - tape[i]) % 256] * (256 - t))
157
+ i += 1
158
+
159
+
160
+ # =============================================================================
161
+ # The constructor program (21 SUBLEQ instructions + 4 variable cells)
162
+ # =============================================================================
163
+
164
+ Z, ONE, T1, T2 = 0xF0, 0xF1, 0xF2, 0xF3
165
+
166
+ CONSTRUCTOR = [
167
+ # main loop: read tag T; T1 = -T (== run length for repeat tags), T2 = T
168
+ (T2, T2, 0x03), # 00 T2 = 0
169
+ (T1, T1, 0x06), # 03 T1 = 0
170
+ (IN_DATA, T1, 0x09), # 06 T1 = -T
171
+ (Z, IN_STROBE, 0x0C), # 09 consume tag byte
172
+ (T1, T2, 0x0F), # 0C T2 = T
173
+ (Z, T2, 0x15), # 0F if T == 0 or T >= 128 -> 15, else literal
174
+ (Z, Z, 0x24), # 12 goto LITERAL
175
+ (Z, T1, 0x1B), # 15 T1 = -T: leq only when T == 0 -> END
176
+ (Z, Z, 0x30), # 18 goto REPEAT (T1 holds 256-T = run length)
177
+ (Z, Z, HALT_PC), # 1B END: halt
178
+ (Z, Z, 0x21), # 1E (pad)
179
+ (Z, Z, 0x24), # 21 (pad)
180
+ (IN_DATA, OUT, 0x27), # 24 LITERAL: emit 0 - tape_byte
181
+ (Z, IN_STROBE, 0x2A), # 27 consume it
182
+ (ONE, T2, 0x00), # 2A T2 -= 1; done -> main loop
183
+ (Z, Z, 0x24), # 2D next literal byte
184
+ (IN_DATA, OUT, 0x33), # 30 REPEAT: emit 0 - value (no strobe)
185
+ (ONE, T1, 0x39), # 33 T1 -= 1; done -> 39
186
+ (Z, Z, 0x30), # 36 emit again
187
+ (Z, IN_STROBE, 0x3C), # 39 consume the value byte
188
+ (Z, Z, 0x00), # 3C goto main loop
189
+ ]
190
+
191
+
192
+ def constructor_image() -> List[int]:
193
+ mem = [0] * 256
194
+ for idx, (a, b, c) in enumerate(CONSTRUCTOR):
195
+ mem[idx * 3] = a
196
+ mem[idx * 3 + 1] = b
197
+ mem[idx * 3 + 2] = c
198
+ mem[ONE] = 1
199
+ return mem
200
+
201
+
202
+ # =============================================================================
203
+ # Backend 1: fast pure-integer reference with devices
204
+ # =============================================================================
205
+
206
+ def ref_construct(tape: bytes, max_steps: int = 1 << 34,
207
+ ) -> Tuple[bytes, int]:
208
+ mem = constructor_image()
209
+ mem[IN_DATA] = tape[0] if tape else 0
210
+ out = bytearray()
211
+ head = 0
212
+ pc = 0
213
+ steps = 0
214
+ tn = len(tape)
215
+ while pc != HALT_PC and steps < max_steps:
216
+ A = mem[pc]
217
+ Bc = mem[(pc + 1) & 0xFF]
218
+ C = mem[(pc + 2) & 0xFF]
219
+ r = (mem[Bc] - mem[A]) & 0xFF
220
+ mem[Bc] = r
221
+ pc = C if (r == 0 or r >= 0x80) else (pc + 3) & 0xFF
222
+ if Bc == OUT:
223
+ out.append(r)
224
+ mem[OUT] = 0
225
+ elif Bc == IN_STROBE:
226
+ head += 1
227
+ mem[IN_STROBE] = 0
228
+ mem[IN_DATA] = tape[head] if head < tn else 0
229
+ steps += 1
230
+ return bytes(out), steps
231
+
232
+
233
+ # =============================================================================
234
+ # Backend 2: the gate-graph processor (shipped netlist) with devices
235
+ # =============================================================================
236
+
237
+ class GateHost:
238
+ def __init__(self, path: str = MODEL_PATH):
239
+ from machines import SubleqThresholdCPU, load_tensors
240
+ from eval import load_metadata
241
+ T = load_tensors(path)
242
+ reg = load_metadata(path)["signal_registry"]
243
+ self.cpu = SubleqThresholdCPU(T, reg)
244
+
245
+ def run(self, tape: bytes, max_steps: int) -> Tuple[bytes, int, dict]:
246
+ s = {"pc": 0, "mem": constructor_image(), "halted": False}
247
+ s["mem"][IN_DATA] = tape[0] if tape else 0
248
+ out = bytearray()
249
+ head = 0
250
+ n = 0
251
+ while not s["halted"] and n < max_steps:
252
+ Bc = s["mem"][(s["pc"] + 1) & 0xFF]
253
+ s = self.cpu.step(s)
254
+ if Bc == OUT:
255
+ out.append(s["mem"][OUT])
256
+ s["mem"][OUT] = 0
257
+ elif Bc == IN_STROBE:
258
+ head += 1
259
+ s["mem"][IN_STROBE] = 0
260
+ s["mem"][IN_DATA] = tape[head] if head < len(tape) else 0
261
+ n += 1
262
+ return bytes(out), n, s
263
+
264
+
265
+ # =============================================================================
266
+ # Backend 3: the host compiled to recurrent ternary matrices (machine 1 tech)
267
+ # =============================================================================
268
+
269
+ def subleq_state_names() -> List[str]:
270
+ names = [f"pc{i}" for i in range(8)] + ["halt"]
271
+ for j in range(256):
272
+ names += [f"m{j}b{i}" for i in range(8)]
273
+ return names
274
+
275
+
276
+ def build_subleq_step_net() -> Tuple[Net, List[str], List[str]]:
277
+ net = Net()
278
+ pc = [f"pc{i}" for i in range(8)]
279
+ halt = "halt"
280
+ mem = [[f"m{j}b{i}" for i in range(8)] for j in range(256)]
281
+
282
+ P = [net.DECODE(f"pd{j}", pc, j) for j in range(256)]
283
+ byte = []
284
+ for o in range(3): # A, B, C operand bytes
285
+ bo = [net.OR(f"f{o}b{bit}",
286
+ [net.AND(f"f{o}a{j}b{bit}", [mem[j][bit], P[(j - o) % 256]])
287
+ for j in range(256)]) for bit in range(8)]
288
+ byte.append(bo)
289
+ A, Bb, C = byte
290
+
291
+ AD = [net.DECODE(f"ad{j}", A, j) for j in range(256)]
292
+ BD = [net.DECODE(f"bd{j}", Bb, j) for j in range(256)]
293
+ x = [net.OR(f"x{bit}", [net.AND(f"x{bit}a{j}", [mem[j][bit], AD[j]])
294
+ for j in range(256)]) for bit in range(8)]
295
+ y = [net.OR(f"y{bit}", [net.AND(f"y{bit}a{j}", [mem[j][bit], BD[j]])
296
+ for j in range(256)]) for bit in range(8)]
297
+
298
+ nx = [net.NOT(f"nx{k}", x[7 - k]) for k in range(8)] # LSB-first
299
+ c = "#1"
300
+ r_l = []
301
+ for k in range(8):
302
+ s_, c = net.FA(f"sub.fa{k}", y[7 - k], nx[k], c)
303
+ r_l.append(s_)
304
+ r = [r_l[7 - i] for i in range(8)]
305
+
306
+ zero = net.NOR("rzero", r)
307
+ leq = net.OR("leq", [r[0], zero])
308
+
309
+ pc_l = [pc[7 - k] for k in range(8)]
310
+ c = "#0"
311
+ p3_l = []
312
+ for k in range(8):
313
+ addend = "#1" if k in (0, 1) else "#0"
314
+ s_, c = net.FA(f"pc3.fa{k}", pc_l[k], addend, c)
315
+ p3_l.append(s_)
316
+ p3 = [p3_l[7 - i] for i in range(8)]
317
+
318
+ pcm = [net.MUX(f"pcm{i}", leq, C[i], p3[i]) for i in range(8)]
319
+ pcn = [net.MUX(f"pcn{i}", halt, pc[i], pcm[i]) for i in range(8)]
320
+ hdec = net.DECODE("hdec", pcn, HALT_PC)
321
+ halt_n = net.OR("haltn", [halt, hdec])
322
+
323
+ nh = net.NOT("nh", halt)
324
+ mem_n = []
325
+ for j in range(256):
326
+ ws = net.AND(f"ws{j}", [BD[j], nh])
327
+ nws = net.NOT(f"nws{j}", ws)
328
+ row = [net.OR(f"mn{j}b{bit}",
329
+ [net.AND(f"mn{j}b{bit}o", [mem[j][bit], nws]),
330
+ net.AND(f"mn{j}b{bit}n", [r[bit], ws])])
331
+ for bit in range(8)]
332
+ mem_n.append(row)
333
+
334
+ outputs = pcn + [halt_n] + [s for row in mem_n for s in row]
335
+ return net, subleq_state_names(), outputs
336
+
337
+
338
+ class MatrixHost:
339
+ """The subleq8io processor as a recurrent ternary linear-threshold
340
+ network, with the device cells poked between recurrences."""
341
+
342
+ N = 8 + 1 + 2048
343
+
344
+ def __init__(self, device: str = "cpu"):
345
+ t0 = time.perf_counter()
346
+ net, inputs, outputs = build_subleq_step_net()
347
+ layers, info = compile_net(net, inputs, outputs)
348
+ for W, Bt in layers:
349
+ vals = set(torch.unique(W).tolist())
350
+ assert vals <= {-1, 0, 1}
351
+ self.W = [W.to(device=device, dtype=torch.float32) for W, _ in layers]
352
+ self.B = [Bt.to(device=device, dtype=torch.float32) for _, Bt in layers]
353
+ self.device = device
354
+ self.info = info
355
+ print(f" host compiled to {info['layers']} ternary matrices "
356
+ f"(max width {info['max_width']}, "
357
+ f"{info['total_weights']:,} weights) in "
358
+ f"{time.perf_counter() - t0:.1f}s on {device}")
359
+
360
+ def _vec(self, pc: int, mem: List[int], halt: bool = False) -> torch.Tensor:
361
+ v = torch.zeros(self.N)
362
+ for k in range(8):
363
+ v[k] = (pc >> (7 - k)) & 1
364
+ v[8] = 1.0 if halt else 0.0
365
+ for j in range(256):
366
+ for k in range(8):
367
+ v[9 + j * 8 + k] = (mem[j] >> (7 - k)) & 1
368
+ return v
369
+
370
+ def step(self, v: torch.Tensor) -> torch.Tensor:
371
+ for W, b in zip(self.W, self.B):
372
+ v = ((v @ W.T + b) >= 0).float()
373
+ return v
374
+
375
+ @staticmethod
376
+ def _byte(vcpu: torch.Tensor, j: int) -> int:
377
+ x = 0
378
+ for k in range(8):
379
+ x = (x << 1) | int(vcpu[9 + j * 8 + k])
380
+ return x
381
+
382
+ def run(self, tape: bytes, max_steps: int, expect: Optional[bytes] = None,
383
+ report_every: int = 100000) -> Tuple[bytes, int]:
384
+ mem = constructor_image()
385
+ mem[IN_DATA] = tape[0] if tape else 0
386
+ v = self._vec(0, mem).unsqueeze(0).to(self.device)
387
+ out = bytearray()
388
+ head = 0
389
+ n = 0
390
+ idx_pch = torch.arange(0, 9, device=self.device)
391
+ t0 = time.perf_counter()
392
+ while n < max_steps:
393
+ front = v[0, idx_pch].tolist()
394
+ if front[8] >= 0.5:
395
+ break
396
+ pcv = 0
397
+ for k in range(8):
398
+ pcv = (pcv << 1) | int(front[k])
399
+ bidx = (pcv + 1) & 0xFF
400
+ bbits = v[0, 9 + bidx * 8: 9 + bidx * 8 + 8].tolist()
401
+ Bc = 0
402
+ for k in range(8):
403
+ Bc = (Bc << 1) | int(bbits[k])
404
+ v = self.step(v)
405
+ n += 1
406
+ if Bc == OUT:
407
+ ob = v[0, 9 + OUT * 8: 9 + OUT * 8 + 8].tolist()
408
+ val = 0
409
+ for k in range(8):
410
+ val = (val << 1) | int(ob[k])
411
+ out.append(val)
412
+ if expect is not None and out[-1] != expect[len(out) - 1]:
413
+ raise AssertionError(
414
+ f"stream diverged at byte {len(out) - 1}: "
415
+ f"got {out[-1]:#04x} want {expect[len(out) - 1]:#04x}")
416
+ v[0, 9 + OUT * 8: 9 + OUT * 8 + 8] = 0.0
417
+ elif Bc == IN_STROBE:
418
+ head += 1
419
+ v[0, 9 + IN_STROBE * 8: 9 + IN_STROBE * 8 + 8] = 0.0
420
+ nxt = tape[head] if head < len(tape) else 0
421
+ for k in range(8):
422
+ v[0, 9 + IN_DATA * 8 + k] = float((nxt >> (7 - k)) & 1)
423
+ if report_every and n % report_every == 0:
424
+ rate = n / (time.perf_counter() - t0)
425
+ print(f" ... {n:,} recurrences, {len(out):,} bytes emitted "
426
+ f"({rate:,.0f} instr/s)")
427
+ return bytes(out), n
428
+
429
+ def exhaustive_datapath(self) -> bool:
430
+ """All 65,536 (x, y) operand pairs through the matrices in one batch."""
431
+ xs = torch.arange(65536) % 256
432
+ ys = torch.arange(65536) // 256
433
+ V = torch.zeros(65536, self.N)
434
+ mem = [0] * 256
435
+ mem[0], mem[1], mem[2] = 0x90, 0x91, 0x30
436
+ v0 = self._vec(0, mem)
437
+ V[:] = v0
438
+ for k in range(8):
439
+ V[:, 9 + 0x90 * 8 + k] = ((xs >> (7 - k)) & 1).float()
440
+ V[:, 9 + 0x91 * 8 + k] = ((ys >> (7 - k)) & 1).float()
441
+ V = V.to(self.device)
442
+ for W, b in zip(self.W, self.B):
443
+ V = ((V @ W.T + b) >= 0).float()
444
+ V = V.cpu()
445
+ r = torch.zeros(65536, dtype=torch.long)
446
+ for k in range(8):
447
+ r = (r << 1) | V[:, 9 + 0x91 * 8 + k].long()
448
+ pcv = torch.zeros(65536, dtype=torch.long)
449
+ for k in range(8):
450
+ pcv = (pcv << 1) | V[:, k].long()
451
+ exp_r = (ys - xs) & 0xFF
452
+ exp_leq = (exp_r == 0) | (exp_r >= 0x80)
453
+ exp_pc = torch.where(exp_leq, torch.full_like(pcv, 0x30),
454
+ torch.full_like(pcv, 3))
455
+ ok = bool((r == exp_r).all()) and bool((pcv == exp_pc).all())
456
+ print(f" {'OK ' if ok else 'FAIL'} matrix datapath exhaustive: "
457
+ f"65,536/65,536 subtract results and branch decisions exact")
458
+ return ok
459
+
460
+
461
+ # =============================================================================
462
+ # Verification
463
+ # =============================================================================
464
+
465
+ def sha(b: bytes) -> str:
466
+ return hashlib.sha256(b).hexdigest()[:16]
467
+
468
+
469
+ def family_files() -> List[str]:
470
+ files = [os.path.join(REPO, "neural_computer.safetensors")]
471
+ vdir = os.path.join(REPO, "variants")
472
+ files += sorted(os.path.join(vdir, f) for f in os.listdir(vdir)
473
+ if f.endswith(".safetensors"))
474
+ return files
475
+
476
+
477
+ def verify(device: str) -> bool:
478
+ ok = True
479
+
480
+ print("\n[1/5] Host machine soundness (shipped netlist, exhaustive datapath)")
481
+ from eval import NetlistEvaluator, load_metadata
482
+ from machines import load_tensors
483
+ T = load_tensors(MODEL_PATH)
484
+ reg = load_metadata(MODEL_PATH)["signal_registry"]
485
+ ne = NetlistEvaluator(T, reg, "subleq")
486
+ a_v = torch.arange(65536) % 256
487
+ b_v = torch.arange(65536) // 256
488
+ ext = {}
489
+ for k in range(8):
490
+ ext[f"$a[{k}]"] = ((a_v >> k) & 1).float()
491
+ ext[f"$b[{k}]"] = ((b_v >> k) & 1).float()
492
+ ext[f"$pc[{k}]"] = torch.zeros(65536)
493
+ ext[f"$c[{k}]"] = torch.zeros(65536)
494
+ o = ne.run(ext)
495
+ rr = sum(o[f"subleq.sub.fa{k}.ha2.sum.layer2"][:, 0].long() << k for k in range(8))
496
+ lq = o["subleq.leq"][:, 0].long()
497
+ exp_r = (b_v - a_v) & 0xFF
498
+ exp_l = ((exp_r == 0) | (exp_r >= 128)).long()
499
+ good = bool((rr == exp_r).all()) and bool((lq == exp_l).all())
500
+ print(f" {'OK ' if good else 'FAIL'} gate netlist exhaustive: 65,536/65,536 exact")
501
+ ok &= good
502
+
503
+ print("\n[2/5] Recipe codec round-trip over the whole family")
504
+ total = 0
505
+ for p in family_files():
506
+ data = open(p, "rb").read()
507
+ tape = describe(data)
508
+ good = decode(tape) == data
509
+ ok &= good
510
+ total += len(data)
511
+ print(f" {'OK ' if good else 'FAIL'} {os.path.basename(p):<44} "
512
+ f"{len(data):>10,} B -> tape {len(tape):>10,} B sha {sha(data)}")
513
+ print(f" ({total / 1e6:.0f} MB described and decoded back, byte-identical)")
514
+
515
+ print("\n[3/5] Constructor program on the integer reference "
516
+ "(output stream == target file, byte for byte)")
517
+ targets = [MODEL_PATH,
518
+ os.path.join(REPO, "variants", "neural_subleq8.safetensors"),
519
+ os.path.join(REPO, "variants", "neural_matrix8.safetensors")]
520
+ for p in targets:
521
+ data = open(p, "rb").read()
522
+ tape = describe(data)
523
+ t0 = time.perf_counter()
524
+ out, steps = ref_construct(tape)
525
+ dt = time.perf_counter() - t0
526
+ good = out == data
527
+ ok &= good
528
+ tag = "SELF-REPRODUCTION" if p == MODEL_PATH else "universal build"
529
+ print(f" {'OK ' if good else 'FAIL'} {os.path.basename(p):<40} "
530
+ f"{len(out):>10,} B in {steps:>12,} instructions ({dt:.1f}s) "
531
+ f"[{tag}] sha {sha(out)}")
532
+
533
+ print("\n[4/5] Matrix backend (machine-1 compile of the host)")
534
+ mh = MatrixHost(device=device)
535
+ ok &= mh.exhaustive_datapath()
536
+
537
+ print("\n[5/5] Backend lockstep on a shared prefix "
538
+ "(gate graph vs matrices vs reference, first 600 cycles)")
539
+ data = open(MODEL_PATH, "rb").read()
540
+ tape = describe(data)
541
+ gh = GateHost()
542
+ s = {"pc": 0, "mem": constructor_image(), "halted": False}
543
+ s["mem"][IN_DATA] = tape[0]
544
+ mem2 = constructor_image()
545
+ mem2[IN_DATA] = tape[0]
546
+ v = mh._vec(0, mem2).unsqueeze(0).to(device)
547
+ headg = headm = 0
548
+ outg, outm = bytearray(), bytearray()
549
+ good = True
550
+ for n in range(600):
551
+ Bg = s["mem"][(s["pc"] + 1) & 0xFF]
552
+ s = gh.cpu.step(s)
553
+ if Bg == OUT:
554
+ outg.append(s["mem"][OUT])
555
+ s["mem"][OUT] = 0
556
+ elif Bg == IN_STROBE:
557
+ headg += 1
558
+ s["mem"][IN_STROBE] = 0
559
+ s["mem"][IN_DATA] = tape[headg] if headg < len(tape) else 0
560
+ vc = v[0].cpu()
561
+ pcm = 0
562
+ for k in range(8):
563
+ pcm = (pcm << 1) | int(vc[k])
564
+ Bm = MatrixHost._byte(vc, (pcm + 1) & 0xFF)
565
+ v = mh.step(v)
566
+ if Bm == OUT:
567
+ outm.append(MatrixHost._byte(v[0].cpu(), OUT))
568
+ v[0, 9 + OUT * 8: 9 + OUT * 8 + 8] = 0.0
569
+ elif Bm == IN_STROBE:
570
+ headm += 1
571
+ v[0, 9 + IN_STROBE * 8: 9 + IN_STROBE * 8 + 8] = 0.0
572
+ nxt = tape[headm] if headm < len(tape) else 0
573
+ for k in range(8):
574
+ v[0, 9 + IN_DATA * 8 + k] = float((nxt >> (7 - k)) & 1)
575
+ vc = v[0].cpu()
576
+ pcm = 0
577
+ for k in range(8):
578
+ pcm = (pcm << 1) | int(vc[k])
579
+ memm = [MatrixHost._byte(vc, j) for j in range(256)]
580
+ if pcm != s["pc"] or memm != s["mem"] or outg != outm:
581
+ print(f" FAIL lockstep diverged at cycle {n}")
582
+ good = False
583
+ break
584
+ print(f" {'OK ' if good else 'FAIL'} gate graph and matrix form agree "
585
+ f"cycle-for-cycle for 600 cycles ({len(outg)} bytes emitted identically)")
586
+ ok &= good
587
+
588
+ print("\nCONSTRUCTOR VERIFICATION:", "PASS" if ok else "FAIL")
589
+ return ok
590
+
591
+
592
+ def self_reproduce(device: str, gen2: bool = True) -> bool:
593
+ """Full self-reproduction at threshold fidelity on the matrix backend."""
594
+ data = open(MODEL_PATH, "rb").read()
595
+ tape = describe(data)
596
+ print(f"\nSelf-reproduction on the threshold matrices: target "
597
+ f"{len(data):,} bytes (sha {sha(data)}), tape {len(tape):,} bytes")
598
+ ref_out, ref_steps = ref_construct(tape)
599
+ assert ref_out == data, "reference construction failed"
600
+ print(f" reference: {ref_steps:,} instructions")
601
+
602
+ mh = MatrixHost(device=device)
603
+ t0 = time.perf_counter()
604
+ out, n = mh.run(tape, max_steps=ref_steps + 64, expect=data)
605
+ dt = time.perf_counter() - t0
606
+ ok = out == data and n == ref_steps
607
+ print(f" GEN 1: emitted {len(out):,} bytes in {n:,} recurrences "
608
+ f"({dt / 60:.1f} min, {n / dt:,.0f} instr/s) sha {sha(out)}")
609
+ print(f" {'OK ' if ok else 'FAIL'} output stream == host safetensors file, "
610
+ f"byte for byte; instruction count matches reference exactly")
611
+
612
+ if ok and gen2:
613
+ off_path = os.path.join(REPO, "variants", "_offspring_subleq8io.safetensors")
614
+ with open(off_path, "wb") as f:
615
+ f.write(out)
616
+ gh = GateHost(off_path) # boot the offspring's own gates
617
+ probe_tape = describe(out[:2048])
618
+ want = out[:2048]
619
+ got, steps, _ = gh.run(probe_tape, max_steps=len(probe_tape) * 8 + 64)
620
+ good2 = got == want
621
+ print(f" GEN 2: offspring file booted as a machine (gate graph); "
622
+ f"constructed the first {len(want):,} bytes of itself in "
623
+ f"{steps:,} instructions: {'exact' if good2 else 'MISMATCH'}")
624
+ os.remove(off_path)
625
+ ok &= good2
626
+ print("SELF-REPRODUCTION:", "PASS" if ok else "FAIL")
627
+ return ok
628
+
629
+
630
+ def main() -> int:
631
+ ap = argparse.ArgumentParser()
632
+ ap.add_argument("cmd", choices=["build", "verify", "self", "all"])
633
+ ap.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
634
+ args = ap.parse_args()
635
+ rc = 0
636
+ if args.cmd in ("build", "all"):
637
+ build_host(save=True)
638
+ if args.cmd in ("verify", "all"):
639
+ rc |= 0 if verify(args.device) else 1
640
+ if args.cmd in ("self", "all"):
641
+ rc |= 0 if self_reproduce(args.device) else 1
642
+ return rc
643
+
644
+
645
+ if __name__ == "__main__":
646
+ sys.exit(main())
eval_all.py CHANGED
@@ -615,16 +615,26 @@ def list_safetensors(path: Path) -> List[Path]:
615
  return []
616
 
617
 
 
 
 
 
 
 
 
 
 
 
 
618
  def evaluate_one(path: Path, device: str, pop_size: int, debug: bool, run_cpu_program: bool) -> Dict:
619
  out: Dict = {"path": str(path), "filename": path.name}
620
- # The standalone machines (neural_subleq8, neural_rv32) carry their own
621
- # circuit inventory and are verified by machines.py, not the gate-fitness
622
- # suite; skip them cleanly rather than error on missing standard gates.
623
  with safe_open(str(path), framework="pt") as f:
624
  meta = f.metadata() or {}
625
  if meta.get("machine"):
626
- out.update(status="SKIP", machine=meta["machine"],
627
- note="standalone machine — verify with machines.py")
 
628
  return out
629
  try:
630
  tensors = load_model(str(path))
@@ -724,7 +734,8 @@ def evaluate_one(path: Path, device: str, pop_size: int, debug: bool, run_cpu_pr
724
 
725
  def print_row(r: Dict, show_cpu: bool) -> None:
726
  if r.get("status") == "SKIP":
727
- print(f" {r['filename']:<48} SKIP ({r.get('machine', 'machine')} — verify with machines.py)")
 
728
  return
729
  if "error" in r:
730
  print(f" {r['filename']:<48} ERROR: {r['error'][:80]}")
 
615
  return []
616
 
617
 
618
+ # Standalone machines carry their own circuit inventory and are verified by
619
+ # their own scripts, not the gate-fitness suite. Each maps to the tool that
620
+ # checks it.
621
+ MACHINE_VERIFIER = {
622
+ "subleq8": "machines.py",
623
+ "rv32": "machines.py",
624
+ "matrix8": "matrix8.py",
625
+ "subleq8io": "constructor8.py",
626
+ }
627
+
628
+
629
  def evaluate_one(path: Path, device: str, pop_size: int, debug: bool, run_cpu_program: bool) -> Dict:
630
  out: Dict = {"path": str(path), "filename": path.name}
631
+ # Skip standalone machines cleanly rather than error on missing standard gates.
 
 
632
  with safe_open(str(path), framework="pt") as f:
633
  meta = f.metadata() or {}
634
  if meta.get("machine"):
635
+ m = meta["machine"]
636
+ out.update(status="SKIP", machine=m,
637
+ note=f"standalone machine — verify with {MACHINE_VERIFIER.get(m, 'machines.py')}")
638
  return out
639
  try:
640
  tensors = load_model(str(path))
 
734
 
735
  def print_row(r: Dict, show_cpu: bool) -> None:
736
  if r.get("status") == "SKIP":
737
+ m = r.get("machine", "machine")
738
+ print(f" {r['filename']:<48} SKIP ({m} — verify with {MACHINE_VERIFIER.get(m, 'machines.py')})")
739
  return
740
  if "error" in r:
741
  print(f" {r['filename']:<48} ERROR: {r['error'][:80]}")
matrix8.py ADDED
@@ -0,0 +1,1081 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """neural_matrix8 — the 8-bit threshold CPU as a recurrent ternary linear-threshold
2
+ network.
3
+
4
+ The verified gate wiring of the registers-profile CPU (8-bit data, 4-bit
5
+ addresses, 16 B memory) is compiled into a fixed sequence of ternary weight
6
+ matrices W1..Wk with a Heaviside step between them, acting on a 173-bit state
7
+ vector that holds the program counter, all four registers, the flags, the
8
+ stack pointer, the halt bit, and every memory bit:
9
+
10
+ [ PC[4] | R0[8] R1[8] R2[8] R3[8] | Z N C V | SP[4] | HALT | MEM[16][8] ]
11
+
12
+ One instruction is one pass through the stack (s' = H(Wk @ ... H(W1 @ s + b1)
13
+ ... + bk)); the machine is the stack iterated until the halt bit sets, so the
14
+ whole processor is a single recurrent linear-threshold network. The transition
15
+ is total: a halted state is a fixed point (every architectural write is gated
16
+ by NOT halt), so iteration past halt is harmless.
17
+
18
+ Every weight is in {-1, 0, 1} and every bias is a small integer; the circuit
19
+ is assembled from the same verified cell constructions the family ships
20
+ (two-layer OR/NAND XOR cells, ha1/ha2/carry_or full adders, bit-cascade
21
+ comparators, 2:1 mux cells, one-threshold-gate address decode rows), then
22
+ levelized ASAP with identity pass-throughs (H(x-1)) carrying live signals
23
+ between layers.
24
+
25
+ Equality with the gate-graph processor is machine-checked, bit for bit:
26
+ - exhaustively over all 65,536 (a, b) operand pairs for every ALU opcode
27
+ (ADD SUB AND OR XOR SHL SHR MUL DIV CMP), batched through the matrices;
28
+ - exhaustively over all Jcc condition x flag combinations, all JMP/CALL
29
+ targets and SP values, and all LOAD/STORE address/data pairs;
30
+ - by full-state lockstep against GenericThresholdCPU walking the shipped
31
+ neural_computer8_registers weights, per instruction, on a micro-program
32
+ suite covering every opcode;
33
+ - by single-step agreement with a pure-integer reference on random states.
34
+
35
+ Analog realization: pre-activations are integers, 1-firing gates sit at >= 0
36
+ and 0-gates at <= -1, so placing the analog comparator threshold at -0.5 gives
37
+ every gate a symmetric guaranteed noise margin of 0.5 — any total analog error
38
+ (read noise + conductance mismatch) below 0.5 per pre-activation provably
39
+ reproduces the digital circuit bit-exactly. The analog suite measures the
40
+ margin, then sweeps Gaussian read noise and static ternary-conductance
41
+ mismatch to locate the empirical breakdown against the guarantee.
42
+
43
+ Usage:
44
+ python matrix8.py build # compile + save variants/neural_matrix8.safetensors
45
+ python matrix8.py verify # equality suite (build in memory if file absent)
46
+ python matrix8.py analog # margin + noise / mismatch sweeps
47
+ python matrix8.py all # build + verify + analog
48
+ """
49
+
50
+ from __future__ import annotations
51
+
52
+ import argparse
53
+ import json
54
+ import os
55
+ import sys
56
+ import time
57
+ from typing import Dict, List, Optional, Tuple
58
+
59
+ import torch
60
+ from safetensors import safe_open
61
+ from safetensors.torch import save_file
62
+
63
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
64
+
65
+ REPO = os.path.dirname(os.path.abspath(__file__))
66
+ MODEL_PATH = os.path.join(REPO, "variants", "neural_matrix8.safetensors")
67
+ GATE_MODEL_PATH = os.path.join(REPO, "variants", "neural_computer8_registers.safetensors")
68
+
69
+ ADDR_BITS = 4
70
+ MEM_BYTES = 16
71
+ STATE_BITS = 4 + 32 + 4 + 4 + 1 + 128 # pc, regs, flags, sp, halt, mem
72
+
73
+
74
+ # =============================================================================
75
+ # Circuit IR: named threshold gates over binary signals, constants fold away
76
+ # =============================================================================
77
+
78
+ class Net:
79
+ """A DAG of threshold gates. Signals are names; '#0' / '#1' are constants
80
+ folded into biases. Helper constructors perform algebraic collapse
81
+ (AND(x,#1)=x, OR with one live input = that input, ...) so the compiled
82
+ stack carries no dead logic; the collapses change nothing semantically and
83
+ the whole machine is verified bit-for-bit afterwards."""
84
+
85
+ def __init__(self):
86
+ self.gates: Dict[str, Tuple[List[Tuple[str, int]], int]] = {}
87
+
88
+ def g(self, name: str, ins: List[Tuple[str, int]], bias: int) -> str:
89
+ folded: List[Tuple[str, int]] = []
90
+ b = int(bias)
91
+ for s, w in ins:
92
+ assert w in (-1, 1), f"non-ternary weight {w} at {name}"
93
+ if s == "#1":
94
+ b += w
95
+ elif s == "#0":
96
+ pass
97
+ else:
98
+ folded.append((s, int(w)))
99
+ if name in self.gates:
100
+ raise ValueError(f"duplicate gate {name}")
101
+ self.gates[name] = (folded, b)
102
+ return name
103
+
104
+ # --- verified cell constructions --------------------------------------
105
+ def NOT(self, name, x):
106
+ if x == "#1":
107
+ return "#0"
108
+ if x == "#0":
109
+ return "#1"
110
+ return self.g(name, [(x, -1)], 0)
111
+
112
+ def BUF(self, name, x):
113
+ if x in ("#0", "#1"):
114
+ return x
115
+ return self.g(name, [(x, 1)], -1)
116
+
117
+ def AND(self, name, ins: List[str]):
118
+ live = []
119
+ for s in ins:
120
+ if s == "#0":
121
+ return "#0"
122
+ if s != "#1":
123
+ live.append(s)
124
+ if not live:
125
+ return "#1"
126
+ if len(live) == 1:
127
+ return live[0]
128
+ return self.g(name, [(s, 1) for s in live], -len(live))
129
+
130
+ def OR(self, name, ins: List[str]):
131
+ live = []
132
+ for s in ins:
133
+ if s == "#1":
134
+ return "#1"
135
+ if s != "#0":
136
+ live.append(s)
137
+ if not live:
138
+ return "#0"
139
+ if len(live) == 1:
140
+ return live[0]
141
+ return self.g(name, [(s, 1) for s in live], -1)
142
+
143
+ def NOR(self, name, ins: List[str]):
144
+ live = [s for s in ins if s != "#0"]
145
+ if any(s == "#1" for s in live):
146
+ return "#0"
147
+ if not live:
148
+ return "#1"
149
+ return self.g(name, [(s, -1) for s in live], 0)
150
+
151
+ def XOR(self, prefix, a, b):
152
+ """Two-layer OR/NAND XOR, the family's verified construction."""
153
+ if a == "#0":
154
+ return b if b in ("#0", "#1") else self.BUF(f"{prefix}.buf", b)
155
+ if b == "#0":
156
+ return a if a in ("#0", "#1") else self.BUF(f"{prefix}.buf", a)
157
+ if a == "#1":
158
+ return self.NOT(f"{prefix}.not", b)
159
+ if b == "#1":
160
+ return self.NOT(f"{prefix}.not", a)
161
+ h_or = self.g(f"{prefix}.l1or", [(a, 1), (b, 1)], -1)
162
+ h_nand = self.g(f"{prefix}.l1nand", [(a, -1), (b, -1)], 1)
163
+ return self.g(prefix, [(h_or, 1), (h_nand, 1)], -2)
164
+
165
+ def FA(self, prefix, a, b, cin):
166
+ """Verified full-adder cell (ha1 XOR/AND, ha2 XOR/AND, carry OR)."""
167
+ s1 = self.XOR(f"{prefix}.ha1s", a, b)
168
+ c1 = self.AND(f"{prefix}.ha1c", [a, b])
169
+ s2 = self.XOR(f"{prefix}.ha2s", s1, cin)
170
+ c2 = self.AND(f"{prefix}.ha2c", [s1, cin])
171
+ co = self.OR(f"{prefix}.cor", [c1, c2])
172
+ return s2, co
173
+
174
+ def MUX(self, prefix, sel, x1, x0):
175
+ """2:1 mux cell: sel ? x1 : x0 (not_sel / and / and / or)."""
176
+ if sel == "#1":
177
+ return x1
178
+ if sel == "#0":
179
+ return x0
180
+ if x1 == x0:
181
+ return x0
182
+ ns = self.NOT(f"{prefix}.ns", sel)
183
+ a1 = self.AND(f"{prefix}.a1", [x1, sel])
184
+ a0 = self.AND(f"{prefix}.a0", [x0, ns])
185
+ return self.OR(f"{prefix}.or", [a1, a0])
186
+
187
+ def DECODE(self, name, bits: List[str], value: int):
188
+ """One-gate address-decode row (the memory.addr_decode construction):
189
+ fires iff the MSB-first bits equal `value`."""
190
+ n = len(bits)
191
+ ins = []
192
+ pop = 0
193
+ for i in range(n):
194
+ bit = (value >> (n - 1 - i)) & 1
195
+ ins.append((bits[i], 1 if bit else -1))
196
+ pop += bit
197
+ return self.g(name, ins, -pop)
198
+
199
+
200
+ # =============================================================================
201
+ # State layout
202
+ # =============================================================================
203
+
204
+ def state_names() -> List[str]:
205
+ names = [f"pc{i}" for i in range(4)]
206
+ for r in range(4):
207
+ names += [f"r{r}b{i}" for i in range(8)]
208
+ names += ["fZ", "fN", "fC", "fV"]
209
+ names += [f"sp{i}" for i in range(4)]
210
+ names += ["halt"]
211
+ for j in range(MEM_BYTES):
212
+ names += [f"m{j}b{i}" for i in range(8)]
213
+ return names
214
+
215
+
216
+ def state_to_vec(s: dict) -> torch.Tensor:
217
+ v = torch.zeros(STATE_BITS)
218
+ i = 0
219
+ for k in range(4):
220
+ v[i] = (s["pc"] >> (3 - k)) & 1
221
+ i += 1
222
+ for r in range(4):
223
+ for k in range(8):
224
+ v[i] = (s["regs"][r] >> (7 - k)) & 1
225
+ i += 1
226
+ for k in range(4):
227
+ v[i] = s["flags"][k]
228
+ i += 1
229
+ for k in range(4):
230
+ v[i] = (s["sp"] >> (3 - k)) & 1
231
+ i += 1
232
+ v[i] = 1.0 if s["halted"] else 0.0
233
+ i += 1
234
+ for j in range(MEM_BYTES):
235
+ for k in range(8):
236
+ v[i] = (s["mem"][j] >> (7 - k)) & 1
237
+ i += 1
238
+ return v
239
+
240
+
241
+ def vec_to_state(v: torch.Tensor) -> dict:
242
+ bits = [int(round(float(x))) for x in v.tolist()]
243
+ i = 0
244
+ pc = 0
245
+ for k in range(4):
246
+ pc = (pc << 1) | bits[i]
247
+ i += 1
248
+ regs = []
249
+ for r in range(4):
250
+ x = 0
251
+ for k in range(8):
252
+ x = (x << 1) | bits[i]
253
+ i += 1
254
+ regs.append(x)
255
+ flags = bits[i:i + 4]
256
+ i += 4
257
+ sp = 0
258
+ for k in range(4):
259
+ sp = (sp << 1) | bits[i]
260
+ i += 1
261
+ halted = bool(bits[i])
262
+ i += 1
263
+ mem = []
264
+ for j in range(MEM_BYTES):
265
+ x = 0
266
+ for k in range(8):
267
+ x = (x << 1) | bits[i]
268
+ i += 1
269
+ mem.append(x)
270
+ return {"pc": pc, "regs": regs, "flags": flags, "sp": sp,
271
+ "halted": halted, "mem": mem}
272
+
273
+
274
+ # =============================================================================
275
+ # The step circuit: one full instruction of the registers-profile CPU
276
+ # =============================================================================
277
+
278
+ def build_step_net() -> Tuple[Net, List[str], List[str]]:
279
+ net = Net()
280
+ pc = [f"pc{i}" for i in range(4)]
281
+ R = [[f"r{r}b{i}" for i in range(8)] for r in range(4)]
282
+ fl = ["fZ", "fN", "fC", "fV"]
283
+ sp = [f"sp{i}" for i in range(4)]
284
+ halt = "halt"
285
+ mem = [[f"m{j}b{i}" for i in range(8)] for j in range(MEM_BYTES)]
286
+
287
+ # ---- fetch: bytes at PC .. PC+3 (decode lines reindexed, no adders) ----
288
+ P = [net.DECODE(f"pdec{j}", pc, j) for j in range(MEM_BYTES)]
289
+ byte = []
290
+ for o in range(4):
291
+ bo = []
292
+ for bit in range(8):
293
+ terms = [net.AND(f"f{o}a{j}b{bit}", [mem[j][bit], P[(j - o) % MEM_BYTES]])
294
+ for j in range(MEM_BYTES)]
295
+ bo.append(net.OR(f"f{o}b{bit}", terms))
296
+ byte.append(bo)
297
+
298
+ # ---- field decode; opcode lines gated by NOT halt (total transition) ---
299
+ nhalt = net.NOT("nhalt", halt)
300
+ O = [net.DECODE(f"op{v:X}", byte[0][0:4], v) for v in range(16)]
301
+ OG = [net.AND(f"og{v:X}", [O[v], nhalt]) for v in range(16)]
302
+ rdl = [net.DECODE(f"rdl{r}", byte[0][4:6], r) for r in range(4)]
303
+ rsl = [net.DECODE(f"rsl{r}", byte[0][6:8], r) for r in range(4)]
304
+ CL = [net.DECODE(f"cond{c}", byte[1][5:8], c) for c in range(8)]
305
+
306
+ # ---- register read ports ----------------------------------------------
307
+ a = [net.OR(f"a{bit}", [net.AND(f"a{bit}r{r}", [R[r][bit], rdl[r]])
308
+ for r in range(4)]) for bit in range(8)]
309
+ b = [net.OR(f"b{bit}", [net.AND(f"b{bit}r{r}", [R[r][bit], rsl[r]])
310
+ for r in range(4)]) for bit in range(8)]
311
+ a_l = lambda k: a[7 - k] # LSB-first views for the adder chains
312
+ b_l = lambda k: b[7 - k]
313
+
314
+ # ---- ALU: ADD ----------------------------------------------------------
315
+ c = "#0"
316
+ add_l = []
317
+ for k in range(8):
318
+ s_, c = net.FA(f"add.fa{k}", a_l(k), b_l(k), c)
319
+ add_l.append(s_)
320
+ add = [add_l[7 - i] for i in range(8)]
321
+ add_c = c
322
+
323
+ # ---- SUB (shared by CMP): a + ~b + 1 -----------------------------------
324
+ nb_l = [net.NOT(f"sub.nb{k}", b_l(k)) for k in range(8)]
325
+ c = "#1"
326
+ sub_l = []
327
+ for k in range(8):
328
+ s_, c = net.FA(f"sub.fa{k}", a_l(k), nb_l[k], c)
329
+ sub_l.append(s_)
330
+ sub = [sub_l[7 - i] for i in range(8)]
331
+ sub_c = c # carry set = no borrow
332
+
333
+ # ---- bitwise / shifts ---------------------------------------------------
334
+ andr = [net.AND(f"land{bit}", [a[bit], b[bit]]) for bit in range(8)]
335
+ orr = [net.OR(f"lor{bit}", [a[bit], b[bit]]) for bit in range(8)]
336
+ xorr = [net.XOR(f"lxor{bit}", a[bit], b[bit]) for bit in range(8)]
337
+ shl = [a[bit + 1] if bit < 7 else "#0" for bit in range(8)]
338
+ shr = [a[bit - 1] if bit > 0 else "#0" for bit in range(8)]
339
+
340
+ # ---- MUL: partial products + 7 chained shift-add adders ----------------
341
+ pp = [[net.AND(f"pp.a{i}b{j}", [a[i], b[j]]) for j in range(8)] for i in range(8)]
342
+ acc_l: List[str] = ["#0"] * 8
343
+ acc_l[7] = pp[7][0] # row j=0 shifted by 7
344
+ for j in range(1, 8):
345
+ shift = 7 - j
346
+ c = "#0"
347
+ nxt = []
348
+ for k in range(8):
349
+ addend = pp[14 - k - j][j] if shift <= k <= min(7, 14 - j) else "#0"
350
+ s_, c = net.FA(f"mul.s{j}.fa{k}", acc_l[k], addend, c)
351
+ nxt.append(s_)
352
+ acc_l = nxt
353
+ mul = [acc_l[7 - i] for i in range(8)]
354
+
355
+ # ---- DIV: 8 restoring stages (bit-cascade GE + subtract + mux) ---------
356
+ def cascade_lt(prefix: str, x_m: List[str], y_m: List[str]) -> str:
357
+ """Unsigned x < y via the verified bit-cascade (bit 0 = MSB)."""
358
+ bit_lt, bit_eq = [], []
359
+ for i in range(8):
360
+ bit_lt.append(net.g(f"{prefix}.b{i}lt", [(x_m[i], -1), (y_m[i], 1)], -1)
361
+ if x_m[i] != "#0" else
362
+ (y_m[i] if y_m[i] != "#0" else "#0"))
363
+ if x_m[i] == "#0":
364
+ bit_eq.append(net.NOT(f"{prefix}.b{i}eq", y_m[i]))
365
+ else:
366
+ e_and = net.AND(f"{prefix}.b{i}ea", [x_m[i], y_m[i]])
367
+ e_nor = net.NOR(f"{prefix}.b{i}en", [x_m[i], y_m[i]])
368
+ bit_eq.append(net.OR(f"{prefix}.b{i}eq", [e_and, e_nor]))
369
+ casc = [bit_lt[0]]
370
+ for i in range(1, 8):
371
+ pref = net.AND(f"{prefix}.ep{i}", bit_eq[:i])
372
+ casc.append(net.AND(f"{prefix}.cl{i}", [pref, bit_lt[i]]))
373
+ return net.OR(f"{prefix}.lt", casc)
374
+
375
+ rem_l: List[str] = ["#0"] * 8
376
+ q_m: List[str] = []
377
+ for st in range(8):
378
+ sh_l = [a[st]] + rem_l[0:7] # (rem << 1) | a_bit; top bit provably 0
379
+ sh_m = [sh_l[7 - i] for i in range(8)]
380
+ lt = cascade_lt(f"div.s{st}.cmp", sh_m, b)
381
+ ge = net.NOT(f"div.s{st}.ge", lt)
382
+ c = "#1"
383
+ dsub_l = []
384
+ for k in range(8):
385
+ s_, c = net.FA(f"div.s{st}.sub.fa{k}", sh_l[k], nb_l[k], c)
386
+ dsub_l.append(s_)
387
+ rem_l = [net.MUX(f"div.s{st}.mx{k}", ge, dsub_l[k], sh_l[k]) for k in range(8)]
388
+ q_m.append(ge)
389
+ div = q_m # quotient, MSB-first
390
+
391
+ # ---- LOAD read port (address = low nibble of byte3) --------------------
392
+ addr4 = byte[3][4:8]
393
+ AD = [net.DECODE(f"adec{j}", addr4, j) for j in range(MEM_BYTES)]
394
+ ld = [net.OR(f"ld{bit}", [net.AND(f"ld{bit}a{j}", [mem[j][bit], AD[j]])
395
+ for j in range(MEM_BYTES)]) for bit in range(8)]
396
+
397
+ # ---- result mux over opcode lines ---------------------------------------
398
+ srcs = [(0, add), (1, sub), (2, andr), (3, orr), (4, xorr),
399
+ (5, shl), (6, shr), (7, mul), (8, div), (0xA, ld)]
400
+ res = [net.OR(f"res{bit}", [net.AND(f"res{bit}o{v:X}", [src[bit], OG[v]])
401
+ for v, src in srcs]) for bit in range(8)]
402
+
403
+ # ---- flags ---------------------------------------------------------------
404
+ z_add = net.NOR("zadd", add)
405
+ z_sub = net.NOR("zsub", sub)
406
+ z_mul = net.NOR("zmul", mul)
407
+ v_add = net.AND("vadd", [net.XOR("vadd.x1", a[0], add[0]),
408
+ net.XOR("vadd.x2", b[0], add[0])])
409
+ v_sub = net.AND("vsub", [net.XOR("vsub.x1", a[0], b[0]),
410
+ net.XOR("vsub.x2", a[0], sub[0])])
411
+ f_src = { # per-source (Z, N, C, V); CMP shares the SUB datapath
412
+ 0: (z_add, add[0], add_c, v_add),
413
+ 1: (z_sub, sub[0], sub_c, v_sub),
414
+ 7: (z_mul, mul[0], "#0", "#0"),
415
+ 9: (z_sub, sub[0], sub_c, v_sub),
416
+ }
417
+ fw = net.OR("fw", [OG[0], OG[1], OG[7], OG[9]])
418
+ fl_next = []
419
+ for fi, fn in enumerate("ZNCV"):
420
+ new = net.OR(f"fnew{fn}", [net.AND(f"fnew{fn}o{v:X}", [f_src[v][fi], OG[v]])
421
+ for v in (0, 1, 7, 9)])
422
+ fl_next.append(net.MUX(f"fmux{fn}", fw, new, fl[fi]))
423
+
424
+ # ---- register writeback --------------------------------------------------
425
+ wen = net.OR("wen", [OG[v] for v in (0, 1, 2, 3, 4, 5, 6, 7, 8, 0xA)])
426
+ R_next = []
427
+ for r in range(4):
428
+ wsel = net.AND(f"wsel{r}", [wen, rdl[r]])
429
+ nws = net.NOT(f"nws{r}", wsel)
430
+ row = []
431
+ for bit in range(8):
432
+ t1 = net.AND(f"wb{r}b{bit}n", [res[bit], wsel])
433
+ t0 = net.AND(f"wb{r}b{bit}o", [R[r][bit], nws])
434
+ row.append(net.OR(f"wb{r}b{bit}", [t1, t0]))
435
+ R_next.append(row)
436
+
437
+ # ---- program counter ------------------------------------------------------
438
+ pc_l = [pc[3 - k] for k in range(4)] # LSB-first view
439
+ p2_l = [pc_l[0], net.NOT("pc2.n1", pc_l[1]),
440
+ net.XOR("pc2.x2", pc_l[2], pc_l[1]),
441
+ net.XOR("pc2.x3", pc_l[3], net.AND("pc2.c3", [pc_l[2], pc_l[1]]))]
442
+ p4_l = [pc_l[0], pc_l[1], net.NOT("pc4.n2", pc_l[2]),
443
+ net.XOR("pc4.x3", pc_l[3], pc_l[2])]
444
+ ext = net.OR("ext", [OG[v] for v in (0xA, 0xB, 0xC, 0xD, 0xE)])
445
+ adv_l = [net.MUX(f"padv{k}", ext, p4_l[k], p2_l[k]) for k in range(4)]
446
+ pc_adv = [adv_l[3 - i] for i in range(4)]
447
+
448
+ nfl = [net.NOT(f"nf{fn}", fl[i]) for i, fn in enumerate("ZNCV")]
449
+ cond_flag = [fl[0], nfl[0], fl[2], nfl[2], fl[1], nfl[1], fl[3], nfl[3]]
450
+ condsel = net.OR("condsel", [net.AND(f"ct{cnd}", [CL[cnd], cond_flag[cnd]])
451
+ for cnd in range(8)])
452
+ take = net.OR("take", [OG[0xC], OG[0xE], net.AND("jcct", [OG[0xD], condsel])])
453
+ keeppc = net.OR("keeppc", [halt, OG[0xF]])
454
+ adv = net.AND("adv", [net.NOT("ntake", take), net.NOT("nkeep", keeppc)])
455
+ pc_next = [net.OR(f"pcn{i}", [net.AND(f"pcn{i}t", [addr4[i], take]),
456
+ net.AND(f"pcn{i}k", [pc[i], keeppc]),
457
+ net.AND(f"pcn{i}a", [pc_adv[i], adv])])
458
+ for i in range(4)]
459
+
460
+ # ---- stack pointer (CALL: SP-2 = SP + 14) ---------------------------------
461
+ sp_l = [sp[3 - k] for k in range(4)]
462
+ c = "#0"
463
+ s2_l = []
464
+ for k, addend in enumerate(["#0", "#1", "#1", "#1"]):
465
+ s_, c = net.FA(f"spm2.fa{k}", sp_l[k], addend, c)
466
+ s2_l.append(s_)
467
+ spm2 = [s2_l[3 - i] for i in range(4)]
468
+ sp_next = [net.MUX(f"spn{i}", OG[0xE], spm2[i], sp[i]) for i in range(4)]
469
+
470
+ # ---- halt (sticky) ---------------------------------------------------------
471
+ halt_next = net.OR("haltn", [halt, OG[0xF]])
472
+
473
+ # ---- memory next: STORE write, CALL pushes ret-hi (0) and ret-lo ----------
474
+ SPD = [net.DECODE(f"spdec{j}", sp, j) for j in range(MEM_BYTES)]
475
+ mem_next = []
476
+ for j in range(MEM_BYTES):
477
+ st_sel = net.AND(f"stsel{j}", [OG[0xB], AD[j]])
478
+ ch_sel = net.AND(f"chsel{j}", [OG[0xE], SPD[(j + 1) % MEM_BYTES]])
479
+ cl_sel = net.AND(f"clsel{j}", [OG[0xE], SPD[(j + 2) % MEM_BYTES]])
480
+ keep = net.NOR(f"keep{j}", [st_sel, ch_sel, cl_sel])
481
+ row = []
482
+ for bit in range(8):
483
+ terms = [net.AND(f"mn{j}b{bit}k", [mem[j][bit], keep]),
484
+ net.AND(f"mn{j}b{bit}s", [b[bit], st_sel])]
485
+ if bit >= 4: # ret-lo low nibble carries pc_adv; high nibble is 0
486
+ terms.append(net.AND(f"mn{j}b{bit}c", [pc_adv[bit - 4], cl_sel]))
487
+ row.append(net.OR(f"mn{j}b{bit}", terms))
488
+ mem_next.append(row)
489
+
490
+ outputs = (pc_next
491
+ + [bitsig for row in R_next for bitsig in row]
492
+ + fl_next + sp_next + [halt_next]
493
+ + [bitsig for row in mem_next for bitsig in row])
494
+ return net, state_names(), outputs
495
+
496
+
497
+ # =============================================================================
498
+ # Compiler: levelize ASAP, insert pass-throughs, emit dense ternary matrices
499
+ # =============================================================================
500
+
501
+ def compile_net(net: Net, inputs: List[str], outputs: List[str],
502
+ ) -> Tuple[List[Tuple[torch.Tensor, torch.Tensor]], dict]:
503
+ gates = net.gates
504
+ level: Dict[str, int] = {s: 0 for s in inputs}
505
+
506
+ consumers: Dict[str, List[str]] = {}
507
+ indeg: Dict[str, int] = {}
508
+ for gname, (ins, _) in gates.items():
509
+ deps = [s for s, _ in ins if s in gates]
510
+ indeg[gname] = len(deps)
511
+ for s, _ in ins:
512
+ consumers.setdefault(s, []).append(gname)
513
+ order = [g for g, d in indeg.items() if d == 0]
514
+ i = 0
515
+ while i < len(order):
516
+ for cns in consumers.get(order[i], []):
517
+ indeg[cns] -= 1
518
+ if indeg[cns] == 0:
519
+ order.append(cns)
520
+ i += 1
521
+ if len(order) != len(gates):
522
+ raise ValueError("cycle in step circuit")
523
+ for gname in order:
524
+ ins, _ = gates[gname]
525
+ level[gname] = 1 + max((level[s] for s, _ in ins), default=0)
526
+
527
+ kmax = max(level[gname] for gname in gates)
528
+ K = kmax + 1 # final layer selects the next-state bits in order
529
+
530
+ last_use: Dict[str, int] = {}
531
+ for gname, (ins, _) in gates.items():
532
+ for s, _ in ins:
533
+ last_use[s] = max(last_use.get(s, 0), level[gname])
534
+ for s in outputs:
535
+ if s not in ("#0", "#1"):
536
+ last_use[s] = K
537
+
538
+ by_level: Dict[int, List[str]] = {}
539
+ for gname in gates:
540
+ by_level.setdefault(level[gname], []).append(gname)
541
+
542
+ vec = list(inputs) # V_0
543
+ layers: List[Tuple[torch.Tensor, torch.Tensor]] = []
544
+ for lv in range(1, K + 1):
545
+ if lv < K:
546
+ new_gates = sorted(by_level.get(lv, []))
547
+ passes = [s for s in vec if last_use.get(s, 0) > lv]
548
+ rows = new_gates + passes
549
+ else:
550
+ rows = list(outputs)
551
+ cols = {s: idx for idx, s in enumerate(vec)}
552
+ W = torch.zeros(len(rows), len(vec), dtype=torch.int8)
553
+ B = torch.zeros(len(rows), dtype=torch.int8)
554
+ out_names = []
555
+ for ri, s in enumerate(rows):
556
+ if lv == K and s == "#0":
557
+ B[ri] = -1
558
+ out_names.append(s)
559
+ continue
560
+ if lv == K and s == "#1":
561
+ B[ri] = 0
562
+ out_names.append(s)
563
+ continue
564
+ if s in gates and level[s] == lv:
565
+ ins, bias = gates[s]
566
+ for src, w in ins:
567
+ W[ri, cols[src]] += w
568
+ B[ri] = bias
569
+ else:
570
+ W[ri, cols[s]] = 1
571
+ B[ri] = -1
572
+ out_names.append(s)
573
+ layers.append((W, B))
574
+ vec = out_names
575
+
576
+ if vec != outputs:
577
+ raise AssertionError("final layer does not emit the state vector")
578
+
579
+ info = {
580
+ "layers": K,
581
+ "gates": len(gates),
582
+ "max_width": max(max(W.shape) for W, _ in layers),
583
+ "total_weights": sum(W.numel() for W, _ in layers),
584
+ "widths": [layers[0][0].shape[1]] + [W.shape[0] for W, _ in layers],
585
+ }
586
+ return layers, info
587
+
588
+
589
+ # =============================================================================
590
+ # Recurrent runtime
591
+ # =============================================================================
592
+
593
+ class MatrixMachine:
594
+ """s' = H(Wk @ ... H(W1 @ s + b1) ... + bk); iterate until the halt bit.
595
+
596
+ Digital mode thresholds at 0 (pre-activations are integers). Analog mode
597
+ thresholds at -0.5 (the max-margin comparator point) and can inject
598
+ Gaussian read noise per MVM and static ternary-conductance mismatch."""
599
+
600
+ HALT_IDX = 4 + 32 + 4 + 4 # index of the halt bit in the state vector
601
+
602
+ def __init__(self, layers, device="cpu"):
603
+ self.device = device
604
+ self.W = [W.to(device=device, dtype=torch.float32) for W, _ in layers]
605
+ self.B = [B.to(device=device, dtype=torch.float32) for _, B in layers]
606
+
607
+ @classmethod
608
+ def from_file(cls, path=MODEL_PATH, device="cpu"):
609
+ layers = []
610
+ with safe_open(path, framework="pt") as f:
611
+ n = 0
612
+ while f"matrix.layer{n:03d}.weight" in f.keys():
613
+ layers.append((f.get_tensor(f"matrix.layer{n:03d}.weight"),
614
+ f.get_tensor(f"matrix.layer{n:03d}.bias")))
615
+ n += 1
616
+ if not layers:
617
+ raise FileNotFoundError(f"no matrix layers in {path}")
618
+ return cls(layers, device=device)
619
+
620
+ def perturbed(self, sigma_g: float, seed: int = 0) -> "MatrixMachine":
621
+ """Static conductance mismatch: each nonzero ternary conductance gets a
622
+ fixed Gaussian perturbation (fabrication variation, constant per run)."""
623
+ gen = torch.Generator(device="cpu").manual_seed(seed)
624
+ m = MatrixMachine.__new__(MatrixMachine)
625
+ m.device = self.device
626
+ m.W = []
627
+ m.B = self.B
628
+ for W in self.W:
629
+ d = torch.randn(W.shape, generator=gen).to(self.device) * sigma_g
630
+ m.W.append(W + d * (W != 0))
631
+ return m
632
+
633
+ def step(self, v: torch.Tensor, analog: bool = False,
634
+ noise_sigma: float = 0.0, gen: Optional[torch.Generator] = None,
635
+ ) -> torch.Tensor:
636
+ thresh = -0.5 if analog else 0.0
637
+ for W, b in zip(self.W, self.B):
638
+ pre = v @ W.T + b
639
+ if noise_sigma > 0.0:
640
+ pre = pre + torch.randn(pre.shape, generator=gen,
641
+ device=pre.device) * noise_sigma
642
+ v = (pre >= thresh).float()
643
+ return v
644
+
645
+ def run(self, state: dict, max_cycles: int = 64, **kw) -> Tuple[dict, int]:
646
+ v = state_to_vec(state).unsqueeze(0).to(self.device)
647
+ n = 0
648
+ while n < max_cycles and v[0, self.HALT_IDX] < 0.5:
649
+ v = self.step(v, **kw)
650
+ n += 1
651
+ return vec_to_state(v[0].cpu()), n
652
+
653
+ def min_margin(self, v: torch.Tensor) -> float:
654
+ """Distance of every pre-activation from the analog threshold -0.5."""
655
+ m = float("inf")
656
+ for W, b in zip(self.W, self.B):
657
+ pre = v @ W.T + b
658
+ m = min(m, float((pre + 0.5).abs().min()))
659
+ v = (pre >= 0).float()
660
+ return m
661
+
662
+
663
+ # =============================================================================
664
+ # Pure-integer reference (mirrors GenericThresholdCPU semantics, 4-bit addr)
665
+ # =============================================================================
666
+
667
+ _JCC_FLAG = [0, 0, 2, 2, 1, 1, 3, 3]
668
+
669
+
670
+ def ref_step(s: dict) -> dict:
671
+ if s["halted"]:
672
+ return {k: (list(v) if isinstance(v, list) else v) for k, v in s.items()}
673
+ s = {"pc": s["pc"], "regs": list(s["regs"]), "flags": list(s["flags"]),
674
+ "sp": s["sp"], "halted": s["halted"], "mem": list(s["mem"])}
675
+ M = MEM_BYTES - 1
676
+ pc = s["pc"]
677
+ ir = (s["mem"][pc] << 8) | s["mem"][(pc + 1) & M]
678
+ op, rd, rs = (ir >> 12) & 0xF, (ir >> 10) & 3, (ir >> 8) & 3
679
+ imm = ir & 0xFF
680
+ next_pc = (pc + 2) & M
681
+ addr = None
682
+ if op in (0xA, 0xB, 0xC, 0xD, 0xE):
683
+ al = s["mem"][(next_pc + 1) & M]
684
+ addr = al & M
685
+ next_pc = (next_pc + 2) & M
686
+ a, b = s["regs"][rd], s["regs"][rs]
687
+ result, carry, ovf, wr = a, 0, 0, True
688
+ if op == 0x0:
689
+ result = (a + b) & 0xFF
690
+ carry = 1 if a + b > 0xFF else 0
691
+ ovf = 1 if ((a ^ result) & (b ^ result)) & 0x80 else 0
692
+ elif op == 0x1:
693
+ result = (a - b) & 0xFF
694
+ carry = 1 if a >= b else 0
695
+ ovf = 1 if ((a ^ b) & (a ^ result)) & 0x80 else 0
696
+ elif op == 0x2:
697
+ result = a & b
698
+ elif op == 0x3:
699
+ result = a | b
700
+ elif op == 0x4:
701
+ result = a ^ b
702
+ elif op == 0x5:
703
+ result = (a << 1) & 0xFF
704
+ elif op == 0x6:
705
+ result = a >> 1
706
+ elif op == 0x7:
707
+ result = (a * b) & 0xFF
708
+ elif op == 0x8:
709
+ result = 0xFF if b == 0 else a // b
710
+ elif op == 0x9:
711
+ r2 = (a - b) & 0xFF
712
+ carry = 1 if a >= b else 0
713
+ ovf = 1 if ((a ^ b) & (a ^ r2)) & 0x80 else 0
714
+ s["flags"] = [1 if r2 == 0 else 0, 1 if r2 & 0x80 else 0, carry, ovf]
715
+ wr = False
716
+ elif op == 0xA:
717
+ result = s["mem"][addr]
718
+ elif op == 0xB:
719
+ s["mem"][addr] = b & 0xFF
720
+ wr = False
721
+ elif op == 0xC:
722
+ s["pc"] = addr
723
+ return s
724
+ elif op == 0xD:
725
+ cnd = imm & 7
726
+ flag = s["flags"][_JCC_FLAG[cnd]]
727
+ sel = flag if cnd % 2 == 0 else 1 - flag
728
+ s["pc"] = addr if sel else next_pc
729
+ return s
730
+ elif op == 0xE:
731
+ ret = next_pc
732
+ sp2 = (s["sp"] - 1) & M
733
+ s["mem"][sp2] = (ret >> 8) & 0xFF
734
+ sp2 = (sp2 - 1) & M
735
+ s["mem"][sp2] = ret & 0xFF
736
+ s["sp"] = sp2
737
+ s["pc"] = addr
738
+ return s
739
+ elif op == 0xF:
740
+ s["halted"] = True
741
+ return s
742
+ if wr and op != 0x9:
743
+ s["regs"][rd] = result & 0xFF
744
+ if op in (0x0, 0x1, 0x7):
745
+ s["flags"] = [1 if result == 0 else 0, 1 if result & 0x80 else 0,
746
+ carry, ovf]
747
+ s["pc"] = next_pc
748
+ return s
749
+
750
+
751
+ # =============================================================================
752
+ # Build + save
753
+ # =============================================================================
754
+
755
+ def build(save: bool = True):
756
+ print("Assembling the step circuit from the verified cell library...")
757
+ t0 = time.perf_counter()
758
+ net, inputs, outputs = build_step_net()
759
+ print(f" {len(net.gates)} threshold gates")
760
+ layers, info = compile_net(net, inputs, outputs)
761
+ dt = time.perf_counter() - t0
762
+ print(f" compiled to {info['layers']} ternary matrices in {dt:.1f}s; "
763
+ f"max width {info['max_width']}, "
764
+ f"{info['total_weights']:,} dense weight entries")
765
+ for W, B in layers:
766
+ vals = set(torch.unique(W).tolist())
767
+ assert vals <= {-1, 0, 1}, f"non-ternary matrix: {vals}"
768
+ assert int(B.min()) >= -128 and int(B.max()) <= 127
769
+
770
+ if save:
771
+ tensors = {}
772
+ for i, (W, B) in enumerate(layers):
773
+ tensors[f"matrix.layer{i:03d}.weight"] = W
774
+ tensors[f"matrix.layer{i:03d}.bias"] = B
775
+ tensors["manifest.data_bits"] = torch.tensor([8.0])
776
+ tensors["manifest.addr_bits"] = torch.tensor([float(ADDR_BITS)])
777
+ tensors["manifest.memory_bytes"] = torch.tensor([float(MEM_BYTES)])
778
+ tensors["manifest.registers"] = torch.tensor([4.0])
779
+ tensors["manifest.layers"] = torch.tensor([float(info["layers"])])
780
+ tensors["manifest.state_bits"] = torch.tensor([float(STATE_BITS)])
781
+ tensors["manifest.version"] = torch.tensor([4.0])
782
+ meta = {
783
+ "machine": "matrix8",
784
+ "weight_quantization": "ternary",
785
+ "state_layout": json.dumps(state_names()),
786
+ "analog": json.dumps({
787
+ "comparator_threshold": -0.5,
788
+ "guaranteed_margin": 0.5,
789
+ "note": "pre-activations are integers; any total analog error "
790
+ "below 0.5 per pre-activation is provably bit-exact",
791
+ }),
792
+ }
793
+ save_file(tensors, MODEL_PATH, metadata=meta)
794
+ sz = os.path.getsize(MODEL_PATH) / (1024 * 1024)
795
+ print(f" saved {MODEL_PATH} ({sz:.1f} MB)")
796
+ return layers, info
797
+
798
+
799
+ # =============================================================================
800
+ # Verification: matrix form == gate graph == integer reference, bit for bit
801
+ # =============================================================================
802
+
803
+ def _mk_state(mem=None, regs=(0, 0, 0, 0), flags=(0, 0, 0, 0), pc=0,
804
+ sp=MEM_BYTES - 1, halted=False):
805
+ m = [0] * MEM_BYTES
806
+ if mem:
807
+ for i, x in enumerate(mem):
808
+ m[i] = x & 0xFF
809
+ return {"pc": pc, "regs": list(regs), "flags": list(flags), "sp": sp,
810
+ "halted": halted, "mem": m}
811
+
812
+
813
+ def _instr(op, rd=0, rs=0, imm=0):
814
+ w = ((op & 0xF) << 12) | ((rd & 3) << 10) | ((rs & 3) << 8) | (imm & 0xFF)
815
+ return [(w >> 8) & 0xFF, w & 0xFF]
816
+
817
+
818
+ def verify(mm: MatrixMachine, device: str) -> bool:
819
+ ok_all = True
820
+
821
+ # ---- 1. exhaustive ALU sweeps: all 65,536 operand pairs per opcode ------
822
+ print("\n[1/4] Exhaustive ALU sweeps (65,536 operand pairs x 10 opcodes, "
823
+ "batched through the matrices, vs integer reference)")
824
+ aa = torch.arange(65536) % 256
825
+ bb = torch.arange(65536) // 256
826
+ for op, name in [(0, "ADD"), (1, "SUB"), (2, "AND"), (3, "OR"), (4, "XOR"),
827
+ (5, "SHL"), (6, "SHR"), (7, "MUL"), (8, "DIV"), (9, "CMP")]:
828
+ for f_init in (0, 0xF):
829
+ V = torch.zeros(65536, STATE_BITS)
830
+ prog = _instr(op, rd=0, rs=1) + _instr(0xF)
831
+ base = _mk_state(mem=prog, flags=[(f_init >> (3 - i)) & 1 for i in range(4)])
832
+ V[:] = state_to_vec(base)
833
+ for k in range(8):
834
+ V[:, 4 + k] = ((aa >> (7 - k)) & 1).float() # R0
835
+ V[:, 12 + k] = ((bb >> (7 - k)) & 1).float() # R1
836
+ V = mm.step(V.to(device)).cpu()
837
+ bad = 0
838
+ probe = list(range(0, 65536, 4099)) + [0, 65535, 255, 256, 32768]
839
+ for idx in probe:
840
+ st = _mk_state(mem=prog, regs=(int(aa[idx]), int(bb[idx]), 0, 0),
841
+ flags=base["flags"])
842
+ if vec_to_state(V[idx]) != ref_step(st):
843
+ bad += 1
844
+ # full vectorized compare of the architectural result field
845
+ r0 = sum(V[:, 4 + k].long() << (7 - k) for k in range(8))
846
+ fZ, fN, fC, fV = (V[:, 36].long(), V[:, 37].long(),
847
+ V[:, 38].long(), V[:, 39].long())
848
+ if op == 0:
849
+ exp = (aa + bb) & 0xFF
850
+ elif op in (1, 9):
851
+ exp = (aa - bb) & 0xFF
852
+ elif op == 2:
853
+ exp = aa & bb
854
+ elif op == 3:
855
+ exp = aa | bb
856
+ elif op == 4:
857
+ exp = aa ^ bb
858
+ elif op == 5:
859
+ exp = (aa << 1) & 0xFF
860
+ elif op == 6:
861
+ exp = aa >> 1
862
+ elif op == 7:
863
+ exp = (aa * bb) & 0xFF
864
+ else:
865
+ exp = torch.where(bb == 0, torch.full_like(aa, 0xFF),
866
+ aa // torch.clamp(bb, min=1))
867
+ reg_exp = aa if op == 9 else exp
868
+ n_bad = int((r0 != reg_exp).sum())
869
+ if op in (0, 1, 7, 9):
870
+ zx = (exp == 0).long()
871
+ nx = ((exp & 0x80) > 0).long()
872
+ if op == 0:
873
+ cx = ((aa + bb) > 0xFF).long()
874
+ vx = ((((aa ^ exp) & (bb ^ exp)) & 0x80) > 0).long()
875
+ elif op in (1, 9):
876
+ cx = (aa >= bb).long()
877
+ vx = ((((aa ^ bb) & (aa ^ exp)) & 0x80) > 0).long()
878
+ else:
879
+ cx = torch.zeros_like(aa)
880
+ vx = torch.zeros_like(aa)
881
+ n_bad += int((fZ != zx).sum() + (fN != nx).sum()
882
+ + (fC != cx).sum() + (fV != vx).sum())
883
+ else:
884
+ want = torch.tensor([(f_init >> (3 - i)) & 1 for i in range(4)])
885
+ n_bad += int((torch.stack([fZ, fN, fC, fV], 1)
886
+ != want.unsqueeze(0)).sum())
887
+ status = "OK " if (n_bad == 0 and bad == 0) else "FAIL"
888
+ if n_bad or bad:
889
+ ok_all = False
890
+ if f_init == 0:
891
+ print(f" {status} {name:4} 65,536/65,536 operand pairs exact"
892
+ + ("" if n_bad == 0 else f" ({n_bad} mismatches)"))
893
+
894
+ # ---- 2. exhaustive control-flow sweeps ----------------------------------
895
+ print("\n[2/4] Exhaustive control-flow sweeps")
896
+ cases = []
897
+ for cnd in range(8): # all Jcc x all flag nibbles
898
+ for f in range(16):
899
+ prog = _instr(0xD, imm=cnd) + [0x00, 0x08] + _instr(0xF) + [0] * 2 + _instr(0xF)
900
+ cases.append(_mk_state(mem=prog, flags=[(f >> (3 - i)) & 1 for i in range(4)]))
901
+ for t in range(16): # all JMP targets
902
+ prog = _instr(0xC) + [0x00, t]
903
+ cases.append(_mk_state(mem=prog))
904
+ for spv in range(16): # CALL: every SP value
905
+ prog = _instr(0xE) + [0x00, 0x06] + [0, 0] + _instr(0xF)
906
+ cases.append(_mk_state(mem=prog, sp=spv))
907
+ for ad in range(16): # LOAD/STORE: every address
908
+ prog = _instr(0xA, rd=2) + [0x00, ad]
909
+ st = _mk_state(mem=prog)
910
+ st["mem"][ad] |= 0xA5 if ad >= 4 else st["mem"][ad]
911
+ cases.append(st)
912
+ prog = _instr(0xB, rs=1) + [0x00, ad]
913
+ cases.append(_mk_state(mem=prog, regs=(0, 0x5A, 0, 0)))
914
+ V = torch.stack([state_to_vec(s) for s in cases]).to(device)
915
+ V = mm.step(V).cpu()
916
+ bad = sum(1 for i, s in enumerate(cases) if vec_to_state(V[i]) != ref_step(s))
917
+ print(f" {'OK ' if bad == 0 else 'FAIL'} {len(cases)} directed cases "
918
+ f"(8 Jcc x 16 flag sets, 16 JMP targets, 16 CALL SPs, 32 LOAD/STORE) exact")
919
+ ok_all &= bad == 0
920
+
921
+ # ---- 3. random-state fuzz + halt fixed point -----------------------------
922
+ print("\n[3/4] Random-state fuzz (single step vs integer reference)")
923
+ gen = torch.Generator().manual_seed(0xC0FFEE)
924
+ Vr = (torch.rand(2000, STATE_BITS, generator=gen) < 0.5).float()
925
+ Vr[:, MatrixMachine.HALT_IDX] = 0.0
926
+ out = mm.step(Vr.to(device)).cpu()
927
+ bad = sum(1 for i in range(Vr.shape[0])
928
+ if vec_to_state(out[i]) != ref_step(vec_to_state(Vr[i])))
929
+ print(f" {'OK ' if bad == 0 else 'FAIL'} 2,000 uniformly random full states exact")
930
+ ok_all &= bad == 0
931
+ Vh = (torch.rand(500, STATE_BITS, generator=gen) < 0.5).float()
932
+ Vh[:, MatrixMachine.HALT_IDX] = 1.0
933
+ outh = mm.step(Vh.to(device)).cpu()
934
+ fixed = bool((outh == Vh.to(outh.device)).all())
935
+ print(f" {'OK ' if fixed else 'FAIL'} 500 halted states are exact fixed points")
936
+ ok_all &= fixed
937
+
938
+ # ---- 4. lockstep vs the gate-graph processor (shipped weights) ----------
939
+ print("\n[4/4] Instruction lockstep vs GenericThresholdCPU on "
940
+ "neural_computer8_registers (the gate graph)")
941
+ from eval_all import GenericThresholdCPU
942
+ T = {}
943
+ with safe_open(GATE_MODEL_PATH, framework="pt") as f:
944
+ for nm in f.keys():
945
+ T[nm] = f.get_tensor(nm).float()
946
+ gcpu = GenericThresholdCPU(T)
947
+ progs = [
948
+ ("alu_chain", _mk_state(mem=_instr(0, 0, 1) + _instr(7, 2, 0) + _instr(9, 2, 1)
949
+ + _instr(4, 1, 2) + _instr(0xF),
950
+ regs=(9, 5, 3, 0))),
951
+ ("div_by_zero", _mk_state(mem=_instr(8, 0, 1) + _instr(0xF), regs=(77, 0, 0, 0))),
952
+ ("load_store", _mk_state(mem=_instr(0xA, rd=3) + [0x00, 0x0E]
953
+ + _instr(0xB, rs=3) + [0x00, 0x0F] + _instr(0xF)
954
+ + [0] * 4 + [0xB7, 0x00],
955
+ )),
956
+ ("call", _mk_state(mem=_instr(0xE) + [0x00, 0x06] + _instr(0xF)
957
+ + _instr(0xF), sp=0xF)),
958
+ ("jcc_taken", _mk_state(mem=_instr(0xD, imm=1) + [0x00, 0x06] + _instr(0xF)
959
+ + _instr(0xF), flags=(0, 0, 0, 0))),
960
+ ("shift_mul", _mk_state(mem=_instr(5, 0) + _instr(6, 1) + _instr(7, 0, 1)
961
+ + _instr(0xF), regs=(0x81, 0x81, 0, 0))),
962
+ ]
963
+ for name, st0 in progs:
964
+ sm = dict(st0)
965
+ sg = {"pc": st0["pc"], "regs": list(st0["regs"]), "flags": list(st0["flags"]),
966
+ "mem": list(st0["mem"]), "halted": False, "sp": st0["sp"]}
967
+ good = True
968
+ for _ in range(12):
969
+ if sm["halted"]:
970
+ break
971
+ sm, _n = mm.run(sm, max_cycles=1)
972
+ sg = gcpu.step(sg)
973
+ match = (sm["pc"] == sg["pc"] and sm["regs"] == sg["regs"]
974
+ and sm["flags"] == sg["flags"] and sm["mem"] == sg["mem"]
975
+ and sm["halted"] == sg["halted"]
976
+ and sm["sp"] == sg.get("sp", MEM_BYTES - 1))
977
+ if not match:
978
+ good = False
979
+ break
980
+ good = good and sm["halted"]
981
+ print(f" {'OK ' if good else 'FAIL'} {name}: full-state lockstep, every instruction")
982
+ ok_all &= good
983
+
984
+ print("\nMATRIX == GATE GRAPH == REFERENCE:", "PASS" if ok_all else "FAIL")
985
+ return ok_all
986
+
987
+
988
+ # =============================================================================
989
+ # Analog realization
990
+ # =============================================================================
991
+
992
+ def analog(mm: MatrixMachine, device: str) -> bool:
993
+ ok = True
994
+ print("\nAnalog crossbar simulation "
995
+ "(comparator at -0.5; ternary conductances are exact by construction)")
996
+
997
+ gen = torch.Generator().manual_seed(7)
998
+ Vr = (torch.rand(512, STATE_BITS, generator=gen) < 0.5).float().to(device)
999
+ margin = mm.min_margin(Vr)
1000
+ print(f" measured minimum |pre-activation - (-0.5)| over 512 random states, "
1001
+ f"all layers: {margin:.3f} (guarantee: 0.5)")
1002
+ ok &= abs(margin - 0.5) < 1e-6
1003
+
1004
+ prog = (_instr(0, 0, 1) + _instr(7, 2, 0) + _instr(9, 2, 1) + _instr(0xF))
1005
+ st0 = _mk_state(mem=prog, regs=(9, 5, 3, 0))
1006
+ ref_final, _ = mm.run(dict(st0), max_cycles=8)
1007
+
1008
+ import math
1009
+ rows_per_step = sum(int(W.shape[0]) for W in mm.W)
1010
+ n_steps = 4
1011
+ print(f" Gaussian read noise per MVM ({rows_per_step:,} comparator "
1012
+ f"decisions per instruction; flip iff |noise| >= 0.5):")
1013
+ for sigma in (0.05, 0.08, 0.10, 0.15, 0.25, 0.45):
1014
+ p_flip = math.erfc(0.5 / (sigma * math.sqrt(2.0)))
1015
+ exp_flips = p_flip * rows_per_step * n_steps
1016
+ exact = 0
1017
+ for t in range(20):
1018
+ g = torch.Generator(device=device).manual_seed(1000 + t)
1019
+ v = state_to_vec(st0).unsqueeze(0).to(device)
1020
+ for _ in range(8):
1021
+ if v[0, MatrixMachine.HALT_IDX] >= 0.5:
1022
+ break
1023
+ v = mm.step(v, analog=True, noise_sigma=sigma, gen=g)
1024
+ exact += int(vec_to_state(v[0].cpu()) == ref_final)
1025
+ print(f" sigma={sigma:.2f}: {exact}/20 runs bit-exact "
1026
+ f"(theory: {exp_flips:.2e} expected flips per run)")
1027
+ if exp_flips < 1e-3 and exact != 20:
1028
+ ok = False
1029
+
1030
+ print(" Static ternary-conductance mismatch (device variation, "
1031
+ "10 fabricated instances each):")
1032
+ for sg in (0.02, 0.05, 0.10, 0.20):
1033
+ exact = 0
1034
+ for t in range(10):
1035
+ mmp = mm.perturbed(sg, seed=200 + t)
1036
+ v = state_to_vec(st0).unsqueeze(0).to(device)
1037
+ for _ in range(8):
1038
+ if v[0, MatrixMachine.HALT_IDX] >= 0.5:
1039
+ break
1040
+ v = mmp.step(v, analog=True)
1041
+ exact += int(vec_to_state(v[0].cpu()) == ref_final)
1042
+ print(f" sigma_G={sg:.2f}: {exact}/10 instances bit-exact")
1043
+ if sg <= 0.02 and exact != 10:
1044
+ ok = False
1045
+
1046
+ print("ANALOG REALIZATION:", "PASS (bit-exact within the stated margins)"
1047
+ if ok else "FAIL")
1048
+ return ok
1049
+
1050
+
1051
+ # =============================================================================
1052
+ # CLI
1053
+ # =============================================================================
1054
+
1055
+ def main() -> int:
1056
+ ap = argparse.ArgumentParser(description="neural_matrix8 builder/verifier")
1057
+ ap.add_argument("cmd", choices=["build", "verify", "analog", "all"])
1058
+ ap.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
1059
+ args = ap.parse_args()
1060
+
1061
+ rc = 0
1062
+ layers = None
1063
+ if args.cmd in ("build", "all"):
1064
+ layers, _ = build(save=True)
1065
+ if args.cmd in ("verify", "analog", "all"):
1066
+ if layers is not None:
1067
+ mm = MatrixMachine(layers, device=args.device)
1068
+ elif os.path.exists(MODEL_PATH):
1069
+ mm = MatrixMachine.from_file(MODEL_PATH, device=args.device)
1070
+ else:
1071
+ layers, _ = build(save=False)
1072
+ mm = MatrixMachine(layers, device=args.device)
1073
+ if args.cmd in ("verify", "all"):
1074
+ rc |= 0 if verify(mm, args.device) else 1
1075
+ if args.cmd in ("analog", "all"):
1076
+ rc |= 0 if analog(mm, args.device) else 1
1077
+ return rc
1078
+
1079
+
1080
+ if __name__ == "__main__":
1081
+ sys.exit(main())
tests/test_play.py CHANGED
@@ -15,8 +15,9 @@ import unittest
15
  from contextlib import redirect_stdout
16
 
17
  REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18
- if REPO_ROOT not in sys.path:
19
- sys.path.insert(0, REPO_ROOT)
 
20
 
21
  import play # noqa: E402
22
 
 
15
  from contextlib import redirect_stdout
16
 
17
  REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
18
+ for _p in (REPO_ROOT, os.path.join(REPO_ROOT, "tools")): # play.py lives in tools/
19
+ if _p not in sys.path:
20
+ sys.path.insert(0, _p)
21
 
22
  import play # noqa: E402
23
 
todo.md CHANGED
@@ -9,13 +9,3 @@ Unfinished work.
9
  - The standalone float normalize stages, superseded by the composed pipelines' own normalizers, are removed from the circuit inventory.
10
  - Every gate in the 32-bit multiply circuit resolves its .inputs to a defined signal.
11
  - A machine-checked correctness proof relates the shipped weights to the ISA.
12
- - The processor's full state transition is expressed as a recurrent ternary threshold network and executes as analog matrix-vector products rather than a wired gate graph.
13
- - The verified gate wiring is compiled into a fixed sequence of ternary weight matrices W1..Wk with a Heaviside step between them, acting on a state vector that holds the program counter, registers, flags, and every memory bit.
14
- - One instruction is that fixed stack of threshold layers, and the machine is the stack iterated until the halt bit sets, so the whole processor is a single recurrent linear-threshold network.
15
- - The matrix form is proven equal to the gate-graph processor, so the ISA semantics carry over bit for bit.
16
- - Each weight matrix maps to a resistive or photonic crossbar with conductances drawn from {-1, 0, +1}, and one clock cycle is one analog matrix-vector product followed by thresholding.
17
- - The analog realization is shown to reproduce the digital circuit within the device's conductance-quantization and noise margins.
18
- - A program running on the processor emits the exact tensor file that defines that processor, and builds any other machine in the family from a description of it.
19
- - The program's output stream is the byte-for-byte safetensors file of the running machine, and that equality is machine-checked rather than observed on a single run.
20
- - The same program takes an encoded description of any machine in the family and constructs that machine's weight file, so the constructor is universal and the description selects what is built.
21
- - Self-reproduction is the case where the description handed to the constructor is its own.
 
9
  - The standalone float normalize stages, superseded by the composed pipelines' own normalizers, are removed from the circuit inventory.
10
  - Every gate in the 32-bit multiply circuit resolves its .inputs to a defined signal.
11
  - A machine-checked correctness proof relates the shipped weights to the ISA.
 
 
 
 
 
 
 
 
 
 
build_all.py → tools/build_all.py RENAMED
@@ -26,7 +26,7 @@ from pathlib import Path
26
  import torch
27
  from safetensors import safe_open
28
 
29
- ROOT = Path(__file__).resolve().parent
30
  SEED = ROOT / "neural_computer.safetensors"
31
  OUT_DIR = ROOT / "variants"
32
  OUT_DIR.mkdir(exist_ok=True)
 
26
  import torch
27
  from safetensors import safe_open
28
 
29
+ ROOT = Path(__file__).resolve().parent.parent # repo root (this file lives in tools/)
30
  SEED = ROOT / "neural_computer.safetensors"
31
  OUT_DIR = ROOT / "variants"
32
  OUT_DIR.mkdir(exist_ok=True)
cpu_programs.py → tools/cpu_programs.py RENAMED
File without changes
play.py → tools/play.py RENAMED
@@ -25,7 +25,8 @@ import sys
25
  import torch
26
  from safetensors import safe_open
27
 
28
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
 
29
 
30
  # Reuse the variant-aware CPU runtime from eval_all.py
31
  from eval_all import GenericThresholdCPU, builtin_program
@@ -47,8 +48,7 @@ def main() -> int:
47
  parser = argparse.ArgumentParser(description="Threshold computer playground")
48
  parser.add_argument(
49
  "--model", type=str,
50
- default=os.path.join(os.path.dirname(__file__),
51
- "variants", "neural_computer8_small.safetensors"),
52
  help="Path to a .safetensors variant"
53
  )
54
  parser.add_argument("--skip-cpu", action="store_true",
 
25
  import torch
26
  from safetensors import safe_open
27
 
28
+ REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # this file lives in tools/
29
+ sys.path.insert(0, REPO)
30
 
31
  # Reuse the variant-aware CPU runtime from eval_all.py
32
  from eval_all import GenericThresholdCPU, builtin_program
 
48
  parser = argparse.ArgumentParser(description="Threshold computer playground")
49
  parser.add_argument(
50
  "--model", type=str,
51
+ default=os.path.join(REPO, "variants", "neural_computer8_small.safetensors"),
 
52
  help="Path to a .safetensors variant"
53
  )
54
  parser.add_argument("--skip-cpu", action="store_true",
prune_weights.py → tools/prune_weights.py RENAMED
@@ -7,10 +7,13 @@ Phase 2: Apply all successes at once, binary search if conflicts
7
 
8
  import argparse
9
  import os
 
10
  import time
11
 
12
  import torch
13
  from safetensors.torch import save_file
 
 
14
  from eval import BatchedFitnessEvaluator, create_population, load_model
15
 
16
  torch.manual_seed(0)
 
7
 
8
  import argparse
9
  import os
10
+ import sys
11
  import time
12
 
13
  import torch
14
  from safetensors.torch import save_file
15
+
16
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # repo root
17
  from eval import BatchedFitnessEvaluator, create_population, load_model
18
 
19
  torch.manual_seed(0)
test_cpu.py → tools/test_cpu.py RENAMED
@@ -20,7 +20,9 @@ from pathlib import Path
20
  import torch
21
  from safetensors import safe_open
22
 
23
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
 
 
24
  from eval_all import GenericThresholdCPU, get_manifest
25
  from cpu_programs import SUITE
26
 
@@ -61,8 +63,7 @@ def main() -> int:
61
  parser = argparse.ArgumentParser(description="Run the CPU program suite")
62
  parser.add_argument(
63
  "--model", type=str,
64
- default=os.path.join(os.path.dirname(__file__),
65
- "variants", "neural_computer8_small.safetensors"),
66
  help="Path to .safetensors variant",
67
  )
68
  parser.add_argument(
 
20
  import torch
21
  from safetensors import safe_open
22
 
23
+ REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # this file lives in tools/
24
+ sys.path.insert(0, REPO) # eval_all lives at the repo root
25
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) # cpu_programs is a sibling
26
  from eval_all import GenericThresholdCPU, get_manifest
27
  from cpu_programs import SUITE
28
 
 
63
  parser = argparse.ArgumentParser(description="Run the CPU program suite")
64
  parser.add_argument(
65
  "--model", type=str,
66
+ default=os.path.join(REPO, "variants", "neural_computer8_small.safetensors"),
 
67
  help="Path to .safetensors variant",
68
  )
69
  parser.add_argument(
variants/neural_matrix8.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:837e5984345ad36af3d4297413046690665751db3567f9c7c33f698e33cf5d3b
3
+ size 9523232
variants/neural_subleq8io.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7744f8ba85916ab705f8e608cc49ca9150e3eac2dc5dbbe6c7682acdb16df93a
3
+ size 95306