Prove self-reproduction by the dynamics for the stored host of every word width, with a sign-extending device read and the record count in closed form, check the stored hosts of widths 9 and 10 symbolically and on random states, check the extracted host family for widths 8 to 10, and remove the to-do note
dd2a421 Download src/host.py from phanerozoic/threshold-computers: direct link, hf CLI and curl.
- Browser
- Download file 68.8 kB
-
https://huggingface.co/phanerozoic/threshold-computers/resolve/main/src/host.py
- Command line
-
hf download hf://phanerozoic/threshold-computers/src/host.py
-
curl -L -o host.py https://huggingface.co/phanerozoic/threshold-computers/resolve/main/src/host.py
68.8 kB
| """The SUBLEQ host: its family, serialization, device, constructor, self-reproduction and evaluators.""" | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import json | |
| import os | |
| import struct | |
| import sys | |
| import time | |
| from typing import Dict, List, Tuple, Optional | |
| import numpy as np | |
| import torch | |
| from safetensors import safe_open | |
| from safetensors.torch import save_file | |
| from machines import Net, compile_net | |
| _DT = {torch.float32:"F32", torch.float64:"F64", torch.int64:"I64", | |
| torch.int32:"I32", torch.int16:"I16", torch.int8:"I8", | |
| torch.uint8:"U8", torch.bool:"BOOL"} | |
| def canonical_bytes(tensors, metadata_pairs): | |
| """Canonical safetensors bytes from tensors and metadata pairs in canonical order.""" | |
| header = {} | |
| if metadata_pairs is not None: | |
| header["__metadata__"] = {k: v for k, v in metadata_pairs} | |
| blobs, off = [], 0 | |
| for name, t in tensors: | |
| t = t.contiguous() | |
| b = t.numpy().tobytes() | |
| header[name] = {"dtype": _DT[t.dtype], "shape": list(t.shape), | |
| "data_offsets": [off, off + len(b)]} | |
| blobs.append(b); off += len(b) | |
| s = json.dumps(header, separators=(",", ":"), ensure_ascii=False).encode("utf-8") | |
| pad = (-(8 + len(s))) % 8 | |
| s = s + b" " * pad | |
| return struct.pack("<Q", len(s)) + s + b"".join(blobs) | |
| VERSION = 6 | |
| def io_cells(A: int) -> Dict[str, int]: | |
| S = 1 << A | |
| return {"c_wr": S - 7, "c_eot": S - 6, "c_rw": S - 5, "c_rd": S - 4, | |
| "r_in": S - 3, "r_out": S - 2, "halt_pc": S - 1} | |
| def layout(A: int) -> Dict[str, List[int]]: | |
| return {"pc": [0, A], "halt": [A, 1], "mem": [A + 1, 1 << A, A]} | |
| def state_names(A: int) -> List[str]: | |
| S = 1 << A | |
| return ([f"pc{i}" for i in range(A)] + ["halt"] | |
| + [f"m{j}b{t}" for j in range(S) for t in range(A)]) | |
| def build(A: int, fold: bool = False) -> Tuple[Net, List[str], List[str]]: | |
| """The step netlist of H_A, units in serialization order.""" | |
| S = 1 << A | |
| net = Net() | |
| g = net.g | |
| pc = [f"pc{i}" for i in range(A)] | |
| halt = "halt" | |
| mu = lambda j, t: f"m{j}b{t}" | |
| def lits(bits: List[str], value: int): | |
| """The literals asserting that the MSB-first `bits` spell `value`.""" | |
| return [(bits[i], 1 if (value >> (A - 1 - i)) & 1 else -1) for i in range(A)] | |
| # B1 decode lines of the counter | |
| for j in range(S): | |
| g(f"P{j}", lits(pc, j), -bin(j).count("1")) | |
| # B2 the counter held by the halt bit | |
| for i in range(A): | |
| g(f"HP{i}", [(pc[i], 1), (halt, 1)], -2) | |
| # B3 the fetch conjunctions: operand o, bit t, counter value j | |
| for o in range(3): | |
| for t in range(A): | |
| for j in range(S): | |
| if fold: | |
| g(f"FA{o}_{t}_{j}", [(mu((j + o) % S, t), 1)] + lits(pc, j), -1 - bin(j).count("1")) | |
| else: | |
| g(f"FA{o}_{t}_{j}", [(mu((j + o) % S, t), 1), (f"P{j}", 1)], -2) | |
| # B4 the bits of p + 3, read off the decode lines | |
| for i in range(A): | |
| g(f"Q{i}", [(f"P{j}", 1) for j in range(S) | |
| if (((j + 3) % S) >> (A - 1 - i)) & 1], -1) | |
| # B5 the three operand words A, B, C | |
| for o in range(3): | |
| for t in range(A): | |
| g(f"F{o}_{t}", [(f"FA{o}_{t}_{j}", 1) for j in range(S)], -1) | |
| Aop = [f"F0_{t}" for t in range(A)] | |
| Bop = [f"F1_{t}" for t in range(A)] | |
| Cop = [f"F2_{t}" for t in range(A)] | |
| # B6 decode lines of A and B | |
| if not fold: | |
| for j in range(S): | |
| g(f"PA{j}", lits(Aop, j), -bin(j).count("1")) | |
| for j in range(S): | |
| g(f"PB{j}", lits(Bop, j), -bin(j).count("1")) | |
| # B7 C is the halt address | |
| g("AC", [(c, 1) for c in Cop], -A) | |
| # B8 write enables | |
| for j in range(S): | |
| if fold: | |
| g(f"W{j}", [(halt, -1)] + lits(Bop, j), -bin(j).count("1")) | |
| else: | |
| g(f"W{j}", [(halt, -1), (f"PB{j}", 1)], -1) | |
| # B9 the read conjunctions of M[A] and M[B] | |
| for t in range(A): | |
| for j in range(S): | |
| if fold: | |
| g(f"RX{t}_{j}", [(mu(j, t), 1)] + lits(Aop, j), -1 - bin(j).count("1")) | |
| else: | |
| g(f"RX{t}_{j}", [(mu(j, t), 1), (f"PA{j}", 1)], -2) | |
| for t in range(A): | |
| for j in range(S): | |
| if fold: | |
| g(f"RY{t}_{j}", [(mu(j, t), 1)] + lits(Bop, j), -1 - bin(j).count("1")) | |
| else: | |
| g(f"RY{t}_{j}", [(mu(j, t), 1), (f"PB{j}", 1)], -2) | |
| # B10 the cells kept | |
| for j in range(S): | |
| for t in range(A): | |
| g(f"KP{j}_{t}", [(mu(j, t), 1), (f"W{j}", -1)], -1) | |
| # B11 X = M[A], Y = M[B] | |
| for t in range(A): | |
| g(f"X{t}", [(f"RX{t}_{j}", 1) for j in range(S)], -1) | |
| for t in range(A): | |
| g(f"Y{t}", [(f"RY{t}_{j}", 1) for j in range(S)], -1) | |
| xv = lambda i: f"X{A - 1 - i}" # value bit i, least significant first | |
| yv = lambda i: f"Y{A - 1 - i}" | |
| # B12 propagate signals of Y + not X | |
| for i in range(A - 1): | |
| g(f"PR{i}", [(xv(i), -1), (yv(i), 1)], 0) | |
| # B13 carry terms into bit k: the incoming carry through all lower | |
| # positions, and a generate at j propagated through j+1..k-1 | |
| for k in range(1, A): | |
| g(f"T{k}_n", [(f"PR{i}", 1) for i in range(k)], -k) | |
| for j in range(k): | |
| g(f"T{k}_{j}", [(yv(j), 1), (xv(j), -1)] | |
| + [(f"PR{i}", 1) for i in range(j + 1, k)], -(1 + (k - 1 - j))) | |
| # B14 carries | |
| for k in range(1, A): | |
| g(f"C{k}", [(f"T{k}_n", 1)] + [(f"T{k}_{j}", 1) for j in range(k)], -1) | |
| # B15 X = Y, bit by bit | |
| for i in range(A): | |
| g(f"AX{i}", [(xv(i), 1), (yv(i), 1)], -2) | |
| for i in range(A): | |
| g(f"NX{i}", [(xv(i), -1), (yv(i), -1)], 0) | |
| for i in range(A): | |
| g(f"XN{i}", [(f"AX{i}", 1), (f"NX{i}", 1)], -1) | |
| # B16 | |
| g("EQ", [(f"XN{i}", 1) for i in range(A)], -A) | |
| # B17 the sum bits: bit 0 is X xor Y, bit k the parity of y_k + not x_k + c_k | |
| g("O0", [(yv(0), 1), (xv(0), 1)], -1) | |
| g("N0", [(yv(0), -1), (xv(0), -1)], 1) | |
| g("R0", [("O0", 1), ("N0", 1)], -2) | |
| for k in range(1, A): | |
| for m, b in ((1, 0), (2, -1), (3, -2)): | |
| g(f"H{m}_{k}", [(yv(k), 1), (xv(k), -1), (f"C{k}", 1)], b) | |
| for k in range(1, A): | |
| g(f"R{k}", [(f"H1_{k}", 1), (f"H2_{k}", -1), (f"H3_{k}", 1)], -1) | |
| top = A - 1 | |
| g("LEQ", [(f"H1_{top}", 1), (f"H2_{top}", -1), (f"H3_{top}", 1), ("EQ", 1)], -1) | |
| # B18 the next counter | |
| for i in range(A): | |
| g(f"TA{i}", [(halt, -1), ("LEQ", 1), (Cop[i], 1)], -2) | |
| for i in range(A): | |
| g(f"TB{i}", [(halt, -1), ("LEQ", -1), (f"Q{i}", 1)], -1) | |
| for i in range(A): | |
| g(f"NP{i}", [(f"HP{i}", 1), (f"TA{i}", 1), (f"TB{i}", 1)], -1) | |
| # B19 the halt latch: the next counter is 2^A - 1 | |
| g("HA", [(halt, -1), ("LEQ", 1), ("AC", 1)], -2) | |
| g("HB", [(halt, -1), ("LEQ", -1), (f"P{S - 4}", 1)], -1) | |
| g("NH", [(halt, 1), ("HA", 1), ("HB", 1)], -1) | |
| # B20 the write: bit t of the word is value bit A-1-t of the difference | |
| for j in range(S): | |
| for t in range(A): | |
| g(f"WR{j}_{t}", [(f"R{A - 1 - t}", 1), (f"W{j}", 1)], -2) | |
| for j in range(S): | |
| for t in range(A): | |
| g(f"NM{j}_{t}", [(f"KP{j}_{t}", 1), (f"WR{j}_{t}", 1)], -1) | |
| outputs = ([f"NP{i}" for i in range(A)] + ["NH"] | |
| + [f"NM{j}_{t}" for j in range(S) for t in range(A)]) | |
| return net, state_names(A), outputs | |
| # --------------------------------------------------------------------------- | |
| # serialization | |
| # --------------------------------------------------------------------------- | |
| _DTORD_FAMILY = {"F64": 0, "F32": 1, "F16": 2, "I64": 3, "I32": 4, "I16": 5, "I8": 6} | |
| _NP = {"I8": "<i1", "I16": "<i2", "I32": "<i4"} | |
| def _itype(maxabs: int) -> str: | |
| return "I16" if maxabs <= 32767 else "I32" | |
| def encode(A: int, bias, fanin, pred, nxt, machine: str, lay, io) -> bytes: | |
| """The serialization of an ordered netlist from its four vectors.""" | |
| n, v = len(nxt), len(bias) | |
| t = {"net.bias": ("I8", np.asarray(bias, dtype=np.int64)), | |
| "net.fanin": (_itype(int(np.max(fanin))), np.asarray(fanin, dtype=np.int64)), | |
| "net.next": (_itype(int(np.max(np.abs(nxt)))), np.asarray(nxt, dtype=np.int64)), | |
| "net.pred": (_itype(int(np.max(np.abs(pred)))), np.asarray(pred, dtype=np.int64))} | |
| assert int(t["net.bias"][1].min()) >= -128 and int(t["net.bias"][1].max()) <= 127 | |
| for k, val in (("addr_bits", A), ("data_bits", A), ("memory_words", 1 << A), | |
| ("pc_width", A), ("state_bits", n), ("units", v), | |
| ("version", VERSION)): | |
| t[f"manifest.{k}"] = ("I32", np.asarray([val], dtype=np.int64)) | |
| order = sorted(t, key=lambda k: (_DTORD_FAMILY[t[k][0]], k)) | |
| header = {"__metadata__": { | |
| "machine": machine, "weight_quantization": "ternary", | |
| "signal_registry": json.dumps(lay, separators=(",", ":"), sort_keys=True), | |
| "io": json.dumps(io, separators=(",", ":"), sort_keys=True)}} | |
| blobs, off = [], 0 | |
| for k in order: | |
| dt, arr = t[k] | |
| b = arr.astype(_NP[dt]).tobytes() | |
| header[k] = {"dtype": dt, "shape": [int(arr.size)], "data_offsets": [off, off + len(b)]} | |
| blobs.append(b) | |
| off += len(b) | |
| s = json.dumps(header, separators=(",", ":"), ensure_ascii=False).encode("utf-8") | |
| s += b" " * ((-(8 + len(s))) % 8) | |
| return struct.pack("<Q", len(s)) + s + b"".join(blobs) | |
| def family_sigma_of_net(net: Net, inputs: List[str], outputs: List[str], A: int, | |
| machine: str = None, lay=None, io=None) -> bytes: | |
| """The serialization of a Net in the order its units were built.""" | |
| slot = {s: i for i, s in enumerate(inputs)} | |
| for gname in net.gates: | |
| slot[gname] = len(slot) | |
| bias, fanin, pred = [], [], [] | |
| for gname, (ins, b) in net.gates.items(): | |
| bias.append(b) | |
| fanin.append(len(ins)) | |
| for s, w in ins: | |
| assert w in (-1, 1) | |
| pred.append(w * (slot[s] + 1)) | |
| nxt = [slot[s] for s in outputs] | |
| return encode(A, bias, fanin, pred, nxt, machine or f"subleq{A}", | |
| lay or layout(A), io or io_cells(A)) | |
| def sigma(A: int) -> bytes: | |
| """The serialization of H_A, written block by block with numpy.""" | |
| S = 1 << A | |
| n = A + 1 + A * S | |
| bias, fanin, pred = [], [], [] | |
| base = {} | |
| count = [n] | |
| def block(name: str, size: int): | |
| base[name] = count[0] | |
| count[0] += size | |
| J = np.arange(S, dtype=np.int64) | |
| popc = np.array([bin(j).count("1") for j in range(S)], dtype=np.int64) | |
| bits = ((J[:, None] >> (A - 1 - np.arange(A))[None, :]) & 1) # [j, i] MSB first | |
| lsign = np.where(bits == 1, 1, -1) | |
| MU = lambda j, t: A + 1 + A * j + t # state index | |
| HALT, PC = A, np.arange(A, dtype=np.int64) | |
| def emit(b, f, p): | |
| bias.append(np.asarray(b, dtype=np.int64).ravel()) | |
| fanin.append(np.asarray(f, dtype=np.int64).ravel()) | |
| pred.append(np.asarray(p, dtype=np.int64).ravel()) | |
| # B1 | |
| block("P", S) | |
| emit(-popc, np.full(S, A), (lsign * (PC[None, :] + 1)).ravel()) | |
| # B2 | |
| block("HP", A) | |
| emit(np.full(A, -2), np.full(A, 2), np.stack([PC + 1, np.full(A, HALT + 1)], 1)) | |
| # B3 | |
| block("FA", 3 * A * S) | |
| o, t, j = np.meshgrid(np.arange(3), np.arange(A), J, indexing="ij") | |
| emit(np.full(3 * A * S, -2), np.full(3 * A * S, 2), | |
| np.stack([MU((j + o) % S, t) + 1, base["P"] + j + 1], -1)) | |
| # B4 | |
| block("Q", A) | |
| qf, qp = [], [] | |
| for i in range(A): | |
| sel = J[((((J + 3) % S) >> (A - 1 - i)) & 1) == 1] | |
| qf.append(len(sel)) | |
| qp.append(base["P"] + sel + 1) | |
| emit(np.full(A, -1), qf, np.concatenate(qp)) | |
| # B5 | |
| block("F", 3 * A) | |
| o, t, j = np.meshgrid(np.arange(3), np.arange(A), J, indexing="ij") | |
| emit(np.full(3 * A, -1), np.full(3 * A, S), | |
| base["FA"] + (o * A + t) * S + j + 1) | |
| F = lambda o, t: base["F"] + o * A + t | |
| # B6 | |
| block("PA", S) | |
| emit(-popc, np.full(S, A), (lsign * (F(0, np.arange(A))[None, :] + 1)).ravel()) | |
| block("PB", S) | |
| emit(-popc, np.full(S, A), (lsign * (F(1, np.arange(A))[None, :] + 1)).ravel()) | |
| # B7 | |
| block("AC", 1) | |
| emit([-A], [A], F(2, np.arange(A)) + 1) | |
| # B8 | |
| block("W", S) | |
| emit(np.full(S, -1), np.full(S, 2), | |
| np.stack([np.full(S, -(HALT + 1)), base["PB"] + J + 1], -1)) | |
| # B9 | |
| t, j = np.meshgrid(np.arange(A), J, indexing="ij") | |
| block("RX", A * S) | |
| emit(np.full(A * S, -2), np.full(A * S, 2), | |
| np.stack([MU(j, t) + 1, base["PA"] + j + 1], -1)) | |
| block("RY", A * S) | |
| emit(np.full(A * S, -2), np.full(A * S, 2), | |
| np.stack([MU(j, t) + 1, base["PB"] + j + 1], -1)) | |
| # B10 | |
| j, t = np.meshgrid(J, np.arange(A), indexing="ij") | |
| block("KP", S * A) | |
| emit(np.full(S * A, -1), np.full(S * A, 2), | |
| np.stack([MU(j, t) + 1, -(base["W"] + j + 1)], -1)) | |
| # B11 | |
| t, j = np.meshgrid(np.arange(A), J, indexing="ij") | |
| block("X", A) | |
| emit(np.full(A, -1), np.full(A, S), base["RX"] + t * S + j + 1) | |
| block("Y", A) | |
| emit(np.full(A, -1), np.full(A, S), base["RY"] + t * S + j + 1) | |
| X = lambda i: base["X"] + (A - 1 - i) | |
| Y = lambda i: base["Y"] + (A - 1 - i) | |
| # B12 | |
| block("PR", A - 1) | |
| ii = np.arange(A - 1) | |
| emit(np.zeros(A - 1), np.full(A - 1, 2), np.stack([-(X(ii) + 1), Y(ii) + 1], -1)) | |
| PR = lambda i: base["PR"] + i | |
| # B13 | |
| tb, tf, tp = [], [], [] | |
| tidx = {} | |
| block("T", sum(k + 1 for k in range(1, A))) | |
| cur = base["T"] | |
| for k in range(1, A): | |
| tidx[(k, "n")] = cur; cur += 1 | |
| tb.append(-k); tf.append(k); tp += [PR(i) + 1 for i in range(k)] | |
| for jj in range(k): | |
| tidx[(k, jj)] = cur; cur += 1 | |
| tb.append(-(1 + (k - 1 - jj))); tf.append(2 + (k - 1 - jj)) | |
| tp += [Y(jj) + 1, -(X(jj) + 1)] + [PR(i) + 1 for i in range(jj + 1, k)] | |
| emit(tb, tf, tp) | |
| # B14 | |
| block("C", A - 1) | |
| cp = [] | |
| for k in range(1, A): | |
| cp += [tidx[(k, "n")] + 1] + [tidx[(k, jj)] + 1 for jj in range(k)] | |
| emit(np.full(A - 1, -1), [k + 1 for k in range(1, A)], cp) | |
| C = lambda k: base["C"] + k - 1 | |
| # B15 | |
| ii = np.arange(A) | |
| block("AX", A) | |
| emit(np.full(A, -2), np.full(A, 2), np.stack([X(ii) + 1, Y(ii) + 1], -1)) | |
| block("NX", A) | |
| emit(np.zeros(A), np.full(A, 2), np.stack([-(X(ii) + 1), -(Y(ii) + 1)], -1)) | |
| block("XN", A) | |
| emit(np.full(A, -1), np.full(A, 2), | |
| np.stack([base["AX"] + ii + 1, base["NX"] + ii + 1], -1)) | |
| # B16 | |
| block("EQ", 1) | |
| emit([-A], [A], base["XN"] + ii + 1) | |
| # B17 | |
| block("R0", 3) | |
| emit([-1, 1, -2], [2, 2, 2], | |
| [Y(0) + 1, X(0) + 1, -(Y(0) + 1), -(X(0) + 1), | |
| base["R0"] + 1, base["R0"] + 2]) | |
| block("H", 3 * (A - 1)) | |
| hb, hp = [], [] | |
| for k in range(1, A): | |
| for b in (0, -1, -2): | |
| hb.append(b) | |
| hp += [Y(k) + 1, -(X(k) + 1), C(k) + 1] | |
| emit(hb, np.full(3 * (A - 1), 3), hp) | |
| H = lambda m, k: base["H"] + 3 * (k - 1) + (m - 1) | |
| block("R", A - 1) | |
| rp = [] | |
| for k in range(1, A): | |
| rp += [H(1, k) + 1, -(H(2, k) + 1), H(3, k) + 1] | |
| emit(np.full(A - 1, -1), np.full(A - 1, 3), rp) | |
| Rv = lambda k: base["R0"] + 2 if k == 0 else base["R"] + k - 1 | |
| block("LEQ", 1) | |
| top = A - 1 | |
| emit([-1], [4], [H(1, top) + 1, -(H(2, top) + 1), H(3, top) + 1, base["EQ"] + 1]) | |
| LEQ = base["LEQ"] | |
| # B18 | |
| ii = np.arange(A) | |
| block("TA", A) | |
| emit(np.full(A, -2), np.full(A, 3), | |
| np.stack([np.full(A, -(HALT + 1)), np.full(A, LEQ + 1), F(2, ii) + 1], -1)) | |
| block("TB", A) | |
| emit(np.full(A, -1), np.full(A, 3), | |
| np.stack([np.full(A, -(HALT + 1)), np.full(A, -(LEQ + 1)), base["Q"] + ii + 1], -1)) | |
| block("NP", A) | |
| emit(np.full(A, -1), np.full(A, 3), | |
| np.stack([base["HP"] + ii + 1, base["TA"] + ii + 1, base["TB"] + ii + 1], -1)) | |
| # B19 | |
| block("HA", 3) | |
| emit([-2, -1, -1], [3, 3, 3], | |
| [-(HALT + 1), LEQ + 1, base["AC"] + 1, | |
| -(HALT + 1), -(LEQ + 1), base["P"] + (S - 4) + 1, | |
| HALT + 1, base["HA"] + 1, base["HA"] + 2]) | |
| NH = base["HA"] + 2 | |
| # B20 | |
| j, t = np.meshgrid(J, np.arange(A), indexing="ij") | |
| rdata = np.array([Rv(A - 1 - tt) for tt in range(A)], dtype=np.int64) | |
| block("WR", S * A) | |
| emit(np.full(S * A, -2), np.full(S * A, 2), | |
| np.stack([rdata[t] + 1, base["W"] + j + 1], -1)) | |
| block("NM", S * A) | |
| emit(np.full(S * A, -1), np.full(S * A, 2), | |
| np.stack([base["KP"] + j * A + t + 1, base["WR"] + j * A + t + 1], -1)) | |
| nxt = np.concatenate([base["NP"] + np.arange(A), [NH], | |
| base["NM"] + np.arange(S * A)]) | |
| b = np.concatenate(bias); f = np.concatenate(fanin); p = np.concatenate(pred) | |
| assert len(b) == count[0] - n and int(f.sum()) == len(p) | |
| return encode(A, b, f, p, nxt, f"subleq{A}", layout(A), io_cells(A)) | |
| # constant signals, which Net folds into biases and which therefore appear only | |
| # as a next-state assignment | |
| CONST0, CONST1 = -1, -2 | |
| _DTORD = {"F64": 0, "F32": 1, "F16": 2, "I64": 3, "I32": 4, "I16": 5, "I8": 6, | |
| "U8": 7, "BOOL": 8} | |
| _TAG = {torch.float32: "F32", torch.float64: "F64", torch.int64: "I64", | |
| torch.int32: "I32", torch.int16: "I16", torch.int8: "I8"} | |
| def depths(net: Net) -> Dict[str, int]: | |
| """Depth of every unit: one more than the greatest depth of a predecessor.""" | |
| d: Dict[str, int] = {} | |
| order = topological(net) | |
| for g in order: | |
| ins, _ = net.gates[g] | |
| d[g] = 1 + max((d[s] for s, _ in ins if s in net.gates), default=0) | |
| return d | |
| def topological(net: Net) -> List[str]: | |
| """The units in some order consistent with the predecessor relation.""" | |
| gates = net.gates | |
| indeg = {g: 0 for g in gates} | |
| cons: Dict[str, List[str]] = {} | |
| for g, (ins, _) in gates.items(): | |
| for s, _w in ins: | |
| if s in gates: | |
| indeg[g] += 1 | |
| cons.setdefault(s, []).append(g) | |
| order = [g for g, k in indeg.items() if k == 0] | |
| i = 0 | |
| while i < len(order): | |
| for c in cons.get(order[i], []): | |
| if c in gates: | |
| indeg[c] -= 1 | |
| if indeg[c] == 0: | |
| order.append(c) | |
| i += 1 | |
| if len(order) != len(gates): | |
| raise ValueError("the predecessor relation has a cycle") | |
| return order | |
| def unit_order(net: Net, inputs: List[str]) -> Tuple[List[str], Dict[str, int]]: | |
| """The canonical order of the units, and the signal index of every signal.""" | |
| d = depths(net) | |
| by_level: Dict[int, List[str]] = {} | |
| for g in net.gates: | |
| by_level.setdefault(d[g], []).append(g) | |
| slot = {s: i for i, s in enumerate(inputs)} | |
| order: List[str] = [] | |
| for lv in sorted(by_level): | |
| keyed = [] | |
| for g in by_level[lv]: | |
| ins, b = net.gates[g] | |
| keyed.append(((b, tuple(sorted((slot[s], w) for s, w in ins))), g)) | |
| keyed.sort(key=lambda kv: kv[0]) | |
| seen: Dict[tuple, int] = {} | |
| for key, g in keyed: | |
| if key not in seen: | |
| seen[key] = len(inputs) + len(order) | |
| order.append(g) | |
| slot[g] = seen[key] | |
| return order, slot | |
| def tensors_of_net(net: Net, inputs: List[str], outputs: List[str]): | |
| """The five tensors of the serialization, in canonical unit order.""" | |
| order, slot = unit_order(net, inputs) | |
| bias, fanin, pred = [], [], [] | |
| for g in order: | |
| ins, b = net.gates[g] | |
| bias.append(b) | |
| fanin.append(len(ins)) | |
| for k, w in sorted((slot[s], w) for s, w in ins): | |
| assert w in (-1, 1), f"weight {w} is not an edge" | |
| pred.append(w * (k + 1)) | |
| nxt = [] | |
| for s in outputs: | |
| if s == "#0": | |
| nxt.append(CONST0) | |
| elif s == "#1": | |
| nxt.append(CONST1) | |
| else: | |
| nxt.append(slot[s]) | |
| i8, i16 = torch.int8, torch.int16 | |
| return { | |
| "net.bias": torch.tensor(bias, dtype=i8), | |
| "net.fanin": torch.tensor(fanin, dtype=i16), | |
| "net.next": torch.tensor(nxt, dtype=i16), | |
| "net.pred": torch.tensor(pred, dtype=i16), | |
| } | |
| def sigma_of_net(net: Net, inputs: List[str], outputs: List[str], | |
| machine: str, layout: Dict, io: Dict) -> bytes: | |
| """The serialization of an ordered clocked netlist.""" | |
| slot = {s: i for i, s in enumerate(inputs)} | |
| for g in net.gates: | |
| slot[g] = len(slot) | |
| bias, fanin, pred = [], [], [] | |
| for g, (ins, b) in net.gates.items(): | |
| bias.append(b) | |
| fanin.append(len(ins)) | |
| for s, w in ins: | |
| assert w in (-1, 1), f"weight {w} is not an edge" | |
| assert slot[s] < slot[g], "a predecessor is built after its unit" | |
| pred.append(w * (slot[s] + 1)) | |
| nxt = [CONST0 if s == "#0" else CONST1 if s == "#1" else slot[s] for s in outputs] | |
| return encode(layout["pc"][1], bias, fanin, pred, nxt, machine, layout, io) | |
| def parse(data: bytes) -> Tuple[Dict[str, torch.Tensor], Dict[str, str]]: | |
| """Header and tensors of a safetensors encoding, without a library call.""" | |
| n = struct.unpack("<Q", data[:8])[0] | |
| head = json.loads(data[8:8 + n]) | |
| meta = head.pop("__metadata__", {}) | |
| base = 8 + n | |
| out = {} | |
| dt = {"I8": (torch.int8, 1), "I16": (torch.int16, 2), | |
| "I32": (torch.int32, 4), "I64": (torch.int64, 8), | |
| "F32": (torch.float32, 4)} | |
| for name, spec in head.items(): | |
| ty, w = dt[spec["dtype"]] | |
| lo, hi = spec["data_offsets"] | |
| buf = bytearray(data[base + lo:base + hi]) | |
| t = torch.frombuffer(buf, dtype=ty).reshape(spec["shape"]) | |
| out[name] = t.clone() | |
| return out, meta | |
| def net_of_sigma(data: bytes) -> Tuple[Net, List[str], List[str], Dict[str, str]]: | |
| """The netlist a serialization denotes, with canonical signal names.""" | |
| t, meta = parse(data) | |
| n = int(t["manifest.state_bits"][0]) | |
| v = int(t["manifest.units"][0]) | |
| bias = t["net.bias"].tolist() | |
| fanin = t["net.fanin"].tolist() | |
| pred = t["net.pred"].tolist() | |
| nxt = t["net.next"].tolist() | |
| if len(bias) != v or len(fanin) != v: | |
| raise ValueError("unit count disagrees with the manifest") | |
| if len(pred) != sum(fanin): | |
| raise ValueError("predecessor count disagrees with the fan-in vector") | |
| if len(nxt) != n: | |
| raise ValueError("next-state vector disagrees with the manifest") | |
| inputs = [f"s{i}" for i in range(n)] | |
| name = lambda k: inputs[k] if k < n else f"u{k - n}" | |
| net = Net() | |
| p = 0 | |
| for j in range(v): | |
| ins = [] | |
| for _ in range(fanin[j]): | |
| e = pred[p] | |
| if e == 0: | |
| raise ValueError("predecessor entry carries no weight") | |
| k, w = abs(e) - 1, (1 if e > 0 else -1) | |
| if k >= n + v: | |
| raise ValueError("predecessor index out of range") | |
| if k >= n + j: | |
| raise ValueError("predecessor is not earlier in the order") | |
| ins.append((name(k), w)) | |
| p += 1 | |
| net.g(f"u{j}", ins, bias[j]) | |
| outputs = [] | |
| for k in nxt: | |
| if k == CONST0: | |
| outputs.append("#0") | |
| elif k == CONST1: | |
| outputs.append("#1") | |
| elif 0 <= k < n + v: | |
| outputs.append(name(k)) | |
| else: | |
| raise ValueError("next-state index out of range") | |
| return net, inputs, outputs, meta | |
| HALT_PC = 0xFF | |
| def subleq_state_names() -> List[str]: | |
| names = [f"pc{i}" for i in range(8)] + ["halt"] | |
| for j in range(256): | |
| names += [f"m{j}b{i}" for i in range(8)] | |
| return names | |
| def build_subleq_step_net() -> Tuple[Net, List[str], List[str]]: | |
| """The step netlist of the host: the member H_8 of the constant-depth family.""" | |
| return build(8) | |
| REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| HOST_PATH = os.path.join(REPO, "variants", "neural_subleq8io_netlist.safetensors") | |
| # device cells | |
| C_WR, C_EOT, C_RW, C_RD, R_IN, R_OUT = 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE | |
| # program variables | |
| Z, ONE, T1, T2, NEG1, EOK = 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5 | |
| # ============================================================================= | |
| # Recipe language (grammar and decoder), reused from constructor8 | |
| # ============================================================================= | |
| def describe(data: bytes) -> bytes: | |
| """Compile bytes into a recipe.""" | |
| tape = bytearray() | |
| i, n = 0, len(data) | |
| while i < n: | |
| j = i | |
| while j < n and data[j] == data[i] and j - i < 127: | |
| j += 1 | |
| if j - i >= 4: | |
| tape.append(256 - (j - i)) | |
| tape.append((256 - data[i]) % 256) | |
| i = j | |
| continue | |
| k = i | |
| while k < n and k - i < 127: | |
| m = k | |
| while m < n and data[m] == data[k] and m - k < 4: | |
| m += 1 | |
| if m - k >= 4: | |
| break | |
| k += 1 | |
| k = max(k, i + 1) | |
| tape.append(k - i) | |
| tape.extend((256 - x) % 256 for x in data[i:k]) | |
| i = k | |
| tape.append(0) | |
| return bytes(tape) | |
| def describe_literal(data: bytes) -> bytes: | |
| """The all-literal encoding used in the proof of the length bound.""" | |
| tape = bytearray() | |
| for i in range(0, len(data), 127): | |
| block = data[i:i + 127] | |
| tape.append(len(block)) | |
| tape.extend((256 - x) % 256 for x in block) | |
| tape.append(0) | |
| return bytes(tape) | |
| def decode(tape: bytes) -> bytes: | |
| """delta: the decoding map on well-formed recipes.""" | |
| out = bytearray() | |
| i = 0 | |
| while True: | |
| t = tape[i] | |
| i += 1 | |
| if t == 0: | |
| return bytes(out) | |
| if t <= 127: | |
| for _ in range(t): | |
| out.append((256 - tape[i]) % 256) | |
| i += 1 | |
| elif t == 128: | |
| raise ValueError("tag 128 is reserved") | |
| else: | |
| out.extend([(256 - tape[i]) % 256] * (256 - t)) | |
| i += 1 | |
| # ============================================================================= | |
| # Framing: ser and inst | |
| # ============================================================================= | |
| def lam(k: int) -> bytes: | |
| """Eight-byte little-endian length field.""" | |
| return struct.pack("<Q", k) | |
| def field(u: bytes) -> bytes: | |
| return lam(len(u)) + u | |
| def ser(sigma: bytes, m: bytes, tau: bytes) -> bytes: | |
| assert len(m) == 256 | |
| return field(sigma) + field(m) + tau | |
| def inst(s: bytes) -> Tuple[bytes, bytes, bytes]: | |
| """Partial inverse of ser: recover (sigma, m, tau).""" | |
| if len(s) < 8: | |
| raise ValueError("truncated") | |
| a = struct.unpack("<Q", s[:8])[0] | |
| if len(s) < 8 + a + 8: | |
| raise ValueError("truncated") | |
| sigma = s[8:8 + a] | |
| b = struct.unpack("<Q", s[8 + a:16 + a])[0] | |
| if b != 256: | |
| raise ValueError("memory image is not 256 bytes") | |
| m = s[16 + a:16 + a + b] | |
| if len(m) != b: # declared 256 bytes, fewer present | |
| raise ValueError("truncated memory image") | |
| tau = s[16 + a + b:] | |
| return sigma, m, tau | |
| # ============================================================================= | |
| # Programs | |
| # ============================================================================= | |
| def _emit(prog: List[Tuple[int, int, int]]) -> Dict[int, int]: | |
| mem = {} | |
| for idx, (a, b, c) in enumerate(prog): | |
| mem[idx * 3] = a | |
| mem[idx * 3 + 1] = b | |
| mem[idx * 3 + 2] = c | |
| return mem | |
| # The decoding loop. Addresses are 3k; every branch target below is written as | |
| # an instruction index and resolved to 3*index by _emit. | |
| # | |
| # k0 T2 <- 0 | |
| # k1 T1 <- 0 | |
| # k2 request read of the tag | |
| # k3 T1 <- -T | |
| # k4 T2 <- T | |
| # k5 branch to k7 when T = 0 or T >= 128 | |
| # k6 goto LITERAL | |
| # k7 branch to END when 256-T <= 0, that is T in {0,128} | |
| # k8 goto REPEAT with T1 = 256-T the run length | |
| # k9 LITERAL: request read of the next byte | |
| # k10 R_OUT <- b | |
| # k11 emit | |
| # k12 T2 <- T2-1; branch to k14 when the count is exhausted | |
| # k13 goto k9 | |
| # k14 goto k0 | |
| # k15 REPEAT: request read of the value byte | |
| # k16 R_OUT <- b | |
| # k17 emit | |
| # k18 T1 <- T1-1; branch to k14 when the count is exhausted | |
| # k19 goto k16 | |
| # k20 END | |
| DECODE_LOOP = [ | |
| (T2, T2, 3 * 1), # k0 | |
| (T1, T1, 3 * 2), # k1 | |
| (NEG1, C_RD, 3 * 3), # k2 | |
| (R_IN, T1, 3 * 4), # k3 | |
| (T1, T2, 3 * 5), # k4 | |
| (Z, T2, 3 * 7), # k5 | |
| (Z, Z, 3 * 9), # k6 | |
| (Z, T1, 3 * 20), # k7 | |
| (Z, Z, 3 * 15), # k8 | |
| (NEG1, C_RD, 3 * 10), # k9 | |
| (R_IN, R_OUT, 3 * 11), # k10 | |
| (NEG1, C_WR, 3 * 12), # k11 | |
| (ONE, T2, 3 * 14), # k12 | |
| (Z, Z, 3 * 9), # k13 | |
| (Z, Z, 3 * 0), # k14 | |
| (NEG1, C_RD, 3 * 16), # k15 | |
| (R_IN, R_OUT, 3 * 17), # k16 | |
| (NEG1, C_WR, 3 * 18), # k17 | |
| (ONE, T1, 3 * 14), # k18 | |
| (Z, Z, 3 * 16), # k19 | |
| ] | |
| # P: the constructor. The end token halts. | |
| P = DECODE_LOOP + [(Z, Z, HALT_PC)] | |
| # P*: the end token enters the rewind phase, then the copy phase. | |
| # k20 R: request rewind | |
| # k21 B0: request read | |
| # k22 B1: EOK <- 1-EOT; halt when the end of tape is reached | |
| # k23 B2: Z <- -b | |
| # k24 B3: R_OUT <- b | |
| # k25 B4: emit | |
| # k26 B5: Z <- 0 and go to B0 | |
| P_STAR = DECODE_LOOP + [ | |
| (NEG1, C_RW, 3 * 21), # k20 | |
| (NEG1, C_RD, 3 * 22), # k21 | |
| (C_EOT, EOK, HALT_PC), # k22 | |
| (R_IN, Z, 3 * 24), # k23 | |
| (Z, R_OUT, 3 * 25), # k24 | |
| (NEG1, C_WR, 3 * 26), # k25 | |
| (Z, Z, 3 * 21), # k26 | |
| ] | |
| # P_e: P with an end-of-tape guard after the tag read, so that the machine | |
| # halts on every tape, recipe or not. Instruction j3 assigns | |
| # M[EOK] <- M[EOK] - M[C_EOT]; a successful tag read has cleared C_EOT and | |
| # leaves EOK at 1, while a read at the end of the tape sets it and the result | |
| # 0 transfers control to the halt cell. | |
| P_TOTAL = [ | |
| (T2, T2, 3 * 1), # j0 | |
| (T1, T1, 3 * 2), # j1 | |
| (NEG1, C_RD, 3 * 3), # j2 read the tag | |
| (C_EOT, EOK, HALT_PC), # j3 halt if that read was past the end of the tape | |
| (R_IN, T1, 3 * 5), # j4 | |
| (T1, T2, 3 * 6), # j5 | |
| (Z, T2, 3 * 8), # j6 | |
| (Z, Z, 3 * 10), # j7 | |
| (Z, T1, 3 * 21), # j8 | |
| (Z, Z, 3 * 16), # j9 | |
| (NEG1, C_RD, 3 * 11), # j10 LITERAL | |
| (R_IN, R_OUT, 3 * 12), # j11 | |
| (NEG1, C_WR, 3 * 13), # j12 | |
| (ONE, T2, 3 * 15), # j13 | |
| (Z, Z, 3 * 10), # j14 | |
| (Z, Z, 3 * 0), # j15 | |
| (NEG1, C_RD, 3 * 17), # j16 REPEAT | |
| (R_IN, R_OUT, 3 * 18), # j17 | |
| (NEG1, C_WR, 3 * 19), # j18 | |
| (ONE, T1, 3 * 15), # j19 | |
| (Z, Z, 3 * 17), # j20 | |
| (Z, Z, HALT_PC), # j21 END | |
| ] | |
| def decode_any(tape: bytes, guard: bool) -> Tuple[bytes, bool]: | |
| """The output of the decoding loop on an arbitrary tape.""" | |
| out = bytearray() | |
| h, rin = 0, 0 | |
| while True: | |
| if h < len(tape): | |
| rin = tape[h] | |
| h += 1 | |
| eot = 0 | |
| else: | |
| eot = 1 | |
| if guard and eot: | |
| return bytes(out), True | |
| t = rin | |
| if not guard and eot: | |
| return bytes(out), t in (0, 128) | |
| if t == 0 or t == 128: | |
| return bytes(out), True | |
| reps = 1 if t > 128 else t | |
| count = (256 - t) if t > 128 else 1 | |
| for _ in range(reps): | |
| if h < len(tape): | |
| rin = tape[h] | |
| h += 1 | |
| out.extend([(256 - rin) % 256] * count) | |
| def memory_image(prog: List[Tuple[int, int, int]]) -> List[int]: | |
| """The 256-byte initial memory image holding a program and its constants.""" | |
| mem = [0] * 256 | |
| for addr, val in _emit(prog).items(): | |
| mem[addr] = val & 0xFF | |
| mem[Z] = 0 | |
| mem[ONE] = 1 | |
| mem[T1] = 0 | |
| mem[T2] = 0 | |
| mem[NEG1] = 0xFF | |
| mem[EOK] = 1 | |
| return mem | |
| M_P = memory_image(P) | |
| M_STAR = memory_image(P_STAR) | |
| def program_a(prog: List[Tuple[int, int, int]], A: int) -> List[Tuple[int, int, int]]: | |
| """A program moved to 2^A words: the cells from 0xF0 up name the top sixteen words.""" | |
| top = (1 << A) - 256 | |
| return [tuple(x + top if x >= 0xF0 else x for x in ins) for ins in prog] | |
| def memory_image_a(prog: List[Tuple[int, int, int]], A: int) -> List[int]: | |
| """The initial memory of 2^A words holding a program moved there and its constants.""" | |
| S = 1 << A | |
| top = S - 256 | |
| mem = [0] * S | |
| for addr, val in _emit(program_a(prog, A)).items(): | |
| mem[addr] = val | |
| for cell, val in ((Z, 0), (ONE, 1), (T1, 0), (T2, 0), (NEG1, S - 1), (EOK, 1)): | |
| mem[cell + top] = val | |
| return mem | |
| # ============================================================================= | |
| # Device | |
| # ============================================================================= | |
| class Tape: | |
| """Environment state (tau, h, omega) with the operations of the definition.""" | |
| def __init__(self, tau: bytes): | |
| self.tau = tau | |
| self.h = 0 | |
| self.out = bytearray() | |
| def apply(self, mem: List[int]) -> None: | |
| """One device application to the post-instruction memory image.""" | |
| if mem[C_WR] == 1: | |
| self.out.append(mem[R_OUT]) | |
| mem[R_OUT] = 0 | |
| if mem[C_RD] == 1: | |
| if self.h < len(self.tau): | |
| mem[R_IN] = self.tau[self.h] | |
| mem[C_EOT] = 0 | |
| self.h += 1 | |
| else: | |
| mem[C_EOT] = 1 | |
| if mem[C_RW] == 1: | |
| self.h = 0 | |
| mem[C_WR] = 0 | |
| mem[C_RD] = 0 | |
| mem[C_RW] = 0 | |
| # ============================================================================= | |
| # Evaluator 1: integer reference | |
| # ============================================================================= | |
| def run_reference(mem0: List[int], tau: bytes, max_steps: int = 1 << 34, | |
| expect: Optional[bytes] = None) -> Tuple[bytes, int]: | |
| mem = list(mem0) | |
| dev = Tape(tau) | |
| pc = 0 | |
| steps = 0 | |
| while pc != HALT_PC and steps < max_steps: | |
| A = mem[pc] | |
| B = mem[(pc + 1) & 0xFF] | |
| C = mem[(pc + 2) & 0xFF] | |
| r = (mem[B] - mem[A]) & 0xFF | |
| mem[B] = r | |
| pc = C if (r == 0 or r >= 0x80) else (pc + 3) & 0xFF | |
| n_before = len(dev.out) | |
| dev.apply(mem) | |
| if expect is not None and len(dev.out) > n_before: | |
| k = len(dev.out) - 1 | |
| if k >= len(expect) or dev.out[k] != expect[k]: | |
| raise AssertionError(f"stream diverged at byte {k}") | |
| steps += 1 | |
| return bytes(dev.out), steps | |
| def run_reference_a(mem0: List[int], tau: bytes, A: int, max_steps: int = 1 << 40) -> Tuple[bytes, int]: | |
| """SUBLEQ_A with the device on its top words, a read extending the byte by its sign.""" | |
| S, half = 1 << A, 1 << (A - 1) | |
| io = io_cells(A) | |
| wr, eot, rw, rd, rin, rout, halt = (io[k] for k in ('c_wr', 'c_eot', 'c_rw', 'c_rd', 'r_in', 'r_out', | |
| 'halt_pc')) | |
| mem, out = list(mem0), bytearray() | |
| pc = h = steps = 0 | |
| while pc != halt and steps < max_steps: | |
| a, b, c = mem[pc], mem[(pc + 1) % S], mem[(pc + 2) % S] | |
| r = (mem[b] - mem[a]) % S | |
| mem[b] = r | |
| pc = c if r == 0 or r >= half else (pc + 3) % S | |
| if mem[wr] == 1: | |
| out.append(mem[rout] & 0xFF) | |
| mem[rout] = 0 | |
| if mem[rd] == 1: | |
| if h < len(tau): | |
| v = tau[h] | |
| mem[rin] = v if v < 128 else v + S - 256 | |
| mem[eot] = 0 | |
| h += 1 | |
| else: | |
| mem[eot] = 1 | |
| if mem[rw] == 1: | |
| h = 0 | |
| mem[wr] = mem[rd] = mem[rw] = 0 | |
| steps += 1 | |
| return bytes(out), steps | |
| # ============================================================================= | |
| # The host netlist and its canonical serialization | |
| # ============================================================================= | |
| STATE_LAYOUT = {"pc": [0, 8], "halt": [8, 1], "mem": [9, 256, 8]} | |
| IO_CELLS = {"c_wr": C_WR, "c_eot": C_EOT, "c_rw": C_RW, "c_rd": C_RD, | |
| "r_in": R_IN, "r_out": R_OUT, "halt_pc": HALT_PC} | |
| STATE_BITS = 8 + 1 + 2048 | |
| def host_netlist(): | |
| """The clocked netlist of the host, assembled from its source description.""" | |
| return build_subleq_step_net() | |
| def sigma_host() -> bytes: | |
| """sigma(N_host): the canonical serialization of that netlist.""" | |
| net, inputs, outputs = host_netlist() | |
| return sigma_of_net(net, inputs, outputs, "subleq8", STATE_LAYOUT, | |
| IO_CELLS) | |
| def read_host() -> bytes: | |
| """The distributed serialization of the host.""" | |
| return open(HOST_PATH, "rb").read() | |
| def sha(b: bytes) -> str: | |
| return hashlib.sha256(b).hexdigest() | |
| def tau_star(sigma: bytes, m: List[int]) -> bytes: | |
| return describe(field(sigma) + field(bytes(m))) | |
| # ============================================================================= | |
| # Evaluators of the step map, each built from sigma alone | |
| # ============================================================================= | |
| class _Transducer: | |
| """State marshalling shared by the threshold evaluators.""" | |
| N = STATE_BITS | |
| _DEVCELLS = (C_WR, C_EOT, C_RW, C_RD, R_IN, R_OUT) | |
| def _vec(self, pc: int, mem: List[int]): | |
| torch = self.torch | |
| v = torch.zeros(self.N) | |
| for k in range(8): | |
| v[k] = (pc >> (7 - k)) & 1 | |
| for j in range(256): | |
| for k in range(8): | |
| v[9 + j * 8 + k] = (mem[j] >> (7 - k)) & 1 | |
| return v | |
| def _byte(vc, j: int) -> int: | |
| x = 0 | |
| for k in range(8): | |
| x = (x << 1) | int(vc[9 + j * 8 + k]) | |
| return x | |
| def _set_byte(self, v, j: int, val: int) -> None: | |
| for k in range(8): | |
| v[0, 9 + j * 8 + k] = float((val >> (7 - k)) & 1) | |
| def capture(self): | |
| """Capture one application of the map as a CUDA graph.""" | |
| torch = self.torch | |
| assert self.device.startswith("cuda") | |
| self.gin = torch.zeros(1, self.N, device=self.device) | |
| # every buffer the body writes must stay alive for the life of the | |
| # graph, or the allocator will hand its memory to something else and | |
| # the replay will overwrite that instead | |
| self.gbuf = self._buffers() | |
| for _ in range(5): | |
| self._body(self.gin) | |
| torch.cuda.synchronize() | |
| self.graph = torch.cuda.CUDAGraph() | |
| with torch.cuda.graph(self.graph): | |
| self.gout = self._body(self.gin) | |
| torch.cuda.synchronize() | |
| gen = torch.Generator(device=self.device).manual_seed(3) | |
| probe = (torch.rand(1, self.N, generator=gen, | |
| device=self.device) < 0.5).float() | |
| self.gin.copy_(probe) | |
| self.graph.replay() | |
| torch.cuda.synchronize() | |
| assert bool((self.gout == self._eager(probe)).all()), \ | |
| "the captured graph differs from the eager step" | |
| return self | |
| def _replay(self, v): | |
| # the state lives in gin, which is ordinary memory; gout belongs to the | |
| # graph's private pool and is only ever read as a whole | |
| if v.data_ptr() != self.gin.data_ptr(): | |
| self.gin.copy_(v) | |
| self.graph.replay() | |
| self.gin.copy_(self.gout) | |
| return self.gin | |
| def run(self, mem0: List[int], tau: bytes, max_steps: int, | |
| expect: Optional[bytes] = None, progress: int = 0, | |
| margin: bool = False) -> Tuple[bytes, int]: | |
| """Iterate the map with the device applied after each step.""" | |
| import time | |
| torch = self.torch | |
| v = self._vec(0, mem0).unsqueeze(0).to(self.device) | |
| dev = Tape(tau) | |
| cells = list(self._DEVCELLS) | |
| cell_t = torch.tensor([9 + j * 8 + k for j in cells for k in range(8)], | |
| device=self.device) | |
| pow2_t = torch.tensor([1 << (7 - k) for k in range(8)], | |
| device=self.device, dtype=torch.float32) | |
| shift_t = torch.tensor([7 - k for k in range(8)]) | |
| shadow = [0] * 256 | |
| self.min_margin = float("inf") | |
| n = 0 | |
| t0 = time.perf_counter() | |
| while n < max_steps: | |
| if margin: | |
| self._accumulate_margin(v) | |
| v = self.step(v) | |
| n += 1 | |
| vals = torch.cat([v[0, 8:9], | |
| (v[0, cell_t].reshape(len(cells), 8) * pow2_t) | |
| .sum(-1)]).to("cpu").to(torch.int64).tolist() | |
| for c, j in enumerate(cells): | |
| shadow[j] = vals[1 + c] | |
| before = len(dev.out) | |
| dev.apply(shadow) | |
| new = torch.tensor([shadow[j] for j in cells], dtype=torch.int64) | |
| v[0, cell_t] = (((new.unsqueeze(-1) >> shift_t) & 1) | |
| .reshape(-1).float().to(self.device)) | |
| if expect is not None and len(dev.out) > before: | |
| k = len(dev.out) - 1 | |
| if k >= len(expect) or dev.out[k] != expect[k]: | |
| raise AssertionError(f"stream diverged at byte {k}") | |
| if vals[0] >= 1: | |
| break | |
| if progress and n % progress == 0: | |
| rate = n / (time.perf_counter() - t0) | |
| print(f" {self.tag} {n:,} steps, {len(dev.out):,} bytes " | |
| f"({rate:,.0f} steps/s)", flush=True) | |
| self.seconds = time.perf_counter() - t0 | |
| return bytes(dev.out), n | |
| class NetEvaluator(_Transducer): | |
| """The netlist of sigma, evaluated unit by unit.""" | |
| tag = "net" | |
| def __init__(self, sigma: bytes, device: str = "cpu", graph: bool = False): | |
| import time | |
| import torch | |
| from reflect import Leveled | |
| t0 = time.perf_counter() | |
| self.torch = torch | |
| self.device = device | |
| net, inputs, outputs, meta = net_of_sigma(sigma) | |
| self.net, self.inputs, self.outputs, self.meta = net, inputs, outputs, meta | |
| assert len(inputs) == self.N, "state width disagrees with the layout" | |
| self.lev = Leveled(net, inputs, outputs, device=device) | |
| self.info = {"levels": len(self.lev.plan), "units": len(net.gates), | |
| "entries": sum(len(i) for i, _ in net.gates.values())} | |
| self.graph = None | |
| if graph: | |
| self.capture() | |
| self.build_seconds = time.perf_counter() - t0 | |
| def _buffers(self): | |
| return self.torch.zeros(self.lev.n_sig, 1, device=self.device) | |
| def _body(self, inp): | |
| V = self.gbuf | |
| lev = self.lev | |
| V.zero_() | |
| V[1] = 1.0 | |
| V[lev.in_slots] = inp.T | |
| for idx, w, b, out in lev.plan: | |
| g = V[idx] | |
| V[out] = ((g * w[:, :, None]).sum(1) + b[:, None] >= 0).float() | |
| return V[lev.out_slots].T | |
| def _eager(self, v): | |
| return self.lev.step(v) | |
| def step(self, v): | |
| if self.graph is not None: | |
| return self._replay(v) | |
| return self.lev.step(v) | |
| class LevEvaluator(_Transducer): | |
| """Lev(N) for the netlist N of sigma.""" | |
| tag = "lev" | |
| def __init__(self, sigma: bytes, device: str = "cpu", dense: bool = True, | |
| graph: bool = False): | |
| import time | |
| import torch | |
| from machines import compile_net | |
| t0 = time.perf_counter() | |
| self.torch = torch | |
| self.device = device | |
| self.dense = dense | |
| net, inputs, outputs, meta = net_of_sigma(sigma) | |
| self.net, self.inputs, self.outputs, self.meta = net, inputs, outputs, meta | |
| assert len(inputs) == self.N, "state width disagrees with the layout" | |
| layers, info = compile_net(net, inputs, outputs) | |
| for W, _ in layers: | |
| assert set(torch.unique(W).tolist()) <= {-1, 0, 1} | |
| self.info = dict(info) | |
| self.info["size"] = sum(int(W.shape[0]) for W, _ in layers) | |
| self.info["nonzero"] = sum(int((W != 0).sum()) for W, _ in layers) | |
| if dense: | |
| self.W = [W.to(device=device, dtype=torch.float32) | |
| for W, _ in layers] | |
| self.B = [b.to(device=device, dtype=torch.float32) | |
| for _, b in layers] | |
| else: | |
| pad = device.startswith("cuda") | |
| self.plan = [self._sparsify(W, b, device, pad) for W, b in layers] | |
| self.widths = [int(W.shape[0]) for W, _ in layers] | |
| self.graph = None | |
| if graph: | |
| self.capture() | |
| self.build_seconds = time.perf_counter() - t0 | |
| def _sparsify(W, b, device, pad): | |
| """One layer as the nonzero entries of its rows.""" | |
| import torch | |
| nz = (W != 0) | |
| counts = nz.sum(1) | |
| n = int(W.shape[0]) | |
| sizes = [int(counts.max())] if pad else sorted(set(counts.tolist())) | |
| groups = [] | |
| for k in sizes: | |
| rows = (torch.arange(n) if pad | |
| else torch.nonzero(counts == k, as_tuple=False).flatten()) | |
| idx = torch.zeros(len(rows), max(k, 1), dtype=torch.long) | |
| w = torch.zeros(len(rows), max(k, 1)) | |
| for r, row in enumerate(rows.tolist()): | |
| cols = torch.nonzero(nz[row], as_tuple=False).flatten() | |
| idx[r, :len(cols)] = cols | |
| w[r, :len(cols)] = W[row, cols] | |
| groups.append((rows.to(device), idx.to(device), | |
| w.to(device=device, dtype=torch.float32), | |
| b[rows].to(device=device, dtype=torch.float32))) | |
| return groups | |
| def _buffers(self): | |
| import torch | |
| return [torch.zeros(1, n, device=self.device) for n in self.widths] | |
| def _sparse_step(self, v, buf): | |
| x = v | |
| for groups, y in zip(self.plan, buf): | |
| for rows, idx, w, b in groups: | |
| y[:, rows] = ((x[:, idx] * w).sum(-1) + b >= 0).float() | |
| x = y | |
| return x | |
| def _body(self, inp): | |
| return self._sparse_step(inp, self.gbuf) | |
| def _eager(self, v): | |
| return self._sparse_step(v, [self.torch.zeros(v.shape[0], n, | |
| device=self.device) | |
| for n in self.widths]) | |
| def step(self, v): | |
| if self.dense: | |
| for W, b in zip(self.W, self.B): | |
| v = ((v @ W.T + b) >= 0).float() | |
| return v | |
| if self.graph is not None: | |
| return self._replay(v) | |
| return self._eager(v) | |
| def _accumulate_margin(self, v): | |
| y = v | |
| for W, b in zip(self.W, self.B): | |
| pre = y @ W.T + b | |
| m = float((pre + 0.5).abs().min()) | |
| if m < self.min_margin: | |
| self.min_margin = m | |
| y = (pre >= 0).float() | |
| def step_noisy(self, v, sigma: float, gen): | |
| """One step with additive Gaussian read noise per pre-activation and the comparator at -1/2.""" | |
| torch = self.torch | |
| if self.dense: | |
| for W, b in zip(self.W, self.B): | |
| pre = v @ W.T + b | |
| pre = pre + torch.randn(pre.shape, generator=gen, | |
| device=pre.device) * sigma | |
| v = (pre >= -0.5).float() | |
| return v | |
| x = v | |
| for groups, n in zip(self.plan, self.widths): | |
| y = torch.empty(x.shape[0], n, device=x.device) | |
| for rows, idx, w, b in groups: | |
| pre = (x[:, idx] * w).sum(-1) + b | |
| pre = pre + torch.randn(pre.shape, generator=gen, | |
| device=pre.device) * sigma | |
| y[:, rows] = (pre >= -0.5).float() | |
| x = y | |
| return x | |
| REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # repo root; this module lives in src/ | |
| MODEL_PATH = os.path.join(REPO, "variants", "neural_subleq8io.safetensors") | |
| IN_DATA, IN_STROBE, OUT = 0xFD, 0xFC, 0xFE | |
| # ============================================================================= | |
| # Build the host file (family per-gate style, .inputs wiring, ternary strict) | |
| # ============================================================================= | |
| def build_host(save: bool = True) -> str: | |
| import build as B | |
| from build import quantize_tensors | |
| tensors: Dict[str, torch.Tensor] = {} | |
| B.add_decoder(tensors, 8, 256) | |
| B.add_memory_read_mux(tensors, 256) | |
| B.add_memory_write_cells(tensors, 256) | |
| B.add_subleq_core(tensors) | |
| tensors["manifest.data_bits"] = torch.tensor([8.0]) | |
| tensors["manifest.addr_bits"] = torch.tensor([8.0]) | |
| tensors["manifest.memory_bytes"] = torch.tensor([256.0]) | |
| tensors["manifest.pc_width"] = torch.tensor([8.0]) | |
| tensors["manifest.version"] = torch.tensor([4.0]) | |
| tensors, reg, stats = B.build_inputs(tensors) | |
| tensors, counts, _, _ = quantize_tensors(tensors, ternary=False) | |
| for k, t in tensors.items(): | |
| if k.endswith(".weight") and not k.startswith("manifest."): | |
| tf = t.float() if t.dtype.is_floating_point else t.to(torch.float32) | |
| assert (tf.abs() <= 1).all(), f"non-ternary weight {k}" | |
| meta = { | |
| "machine": "subleq8io", | |
| "weight_quantization": "ternary", | |
| "signal_registry": reg.to_metadata(), | |
| "io": json.dumps({"in_data": IN_DATA, "in_strobe": IN_STROBE, | |
| "out": OUT, "halt_pc": HALT_PC}), | |
| } | |
| if save: | |
| save_file(tensors, MODEL_PATH, metadata=meta) | |
| sz = os.path.getsize(MODEL_PATH) | |
| print(f" built {MODEL_PATH}: {stats['added']} wired gates, " | |
| f"{len(tensors)} tensors, {sz:,} bytes") | |
| return MODEL_PATH | |
| # ============================================================================= | |
| # Recipe codec (the construction-description language) | |
| # ============================================================================= | |
| def c8_describe(data: bytes) -> bytes: | |
| """Compile raw bytes into a construction recipe for the constructor.""" | |
| tape = bytearray() | |
| i, n = 0, len(data) | |
| while i < n: | |
| j = i | |
| while j < n and data[j] == data[i] and j - i < 127: | |
| j += 1 | |
| if j - i >= 4: | |
| tape.append(256 - (j - i)) # repeat tag 129..252 | |
| tape.append((256 - data[i]) % 256) # value, negated | |
| i = j | |
| continue | |
| k = i | |
| while k < n and k - i < 127: # literal until a run starts | |
| m = k | |
| while m < n and data[m] == data[k] and m - k < 4: | |
| m += 1 | |
| if m - k >= 4: | |
| break | |
| k += 1 | |
| k = max(k, i + 1) | |
| tape.append(k - i) # literal tag 1..127 | |
| tape.extend((256 - x) % 256 for x in data[i:k]) | |
| i = k | |
| tape.append(0) # END | |
| return bytes(tape) | |
| def c8_decode(tape: bytes) -> bytes: | |
| """Pure-Python reference semantics of the recipe language.""" | |
| out = bytearray() | |
| i = 0 | |
| while True: | |
| t = tape[i] | |
| i += 1 | |
| if t == 0: | |
| return bytes(out) | |
| if t <= 127: | |
| for _ in range(t): | |
| out.append((256 - tape[i]) % 256) | |
| i += 1 | |
| else: | |
| out.extend([(256 - tape[i]) % 256] * (256 - t)) | |
| i += 1 | |
| # ============================================================================= | |
| # The constructor program (21 SUBLEQ instructions + 4 variable cells) | |
| # ============================================================================= | |
| CONSTRUCTOR = [ | |
| # main loop: read tag T; T1 = -T (== run length for repeat tags), T2 = T | |
| (T2, T2, 0x03), # 00 T2 = 0 | |
| (T1, T1, 0x06), # 03 T1 = 0 | |
| (IN_DATA, T1, 0x09), # 06 T1 = -T | |
| (Z, IN_STROBE, 0x0C), # 09 consume tag byte | |
| (T1, T2, 0x0F), # 0C T2 = T | |
| (Z, T2, 0x15), # 0F if T == 0 or T >= 128 -> 15, else literal | |
| (Z, Z, 0x24), # 12 goto LITERAL | |
| (Z, T1, 0x1B), # 15 T1 = -T: leq only when T == 0 -> END | |
| (Z, Z, 0x30), # 18 goto REPEAT (T1 holds 256-T = run length) | |
| (Z, Z, HALT_PC), # 1B END: halt | |
| (Z, Z, 0x21), # 1E (pad) | |
| (Z, Z, 0x24), # 21 (pad) | |
| (IN_DATA, OUT, 0x27), # 24 LITERAL: emit 0 - tape_byte | |
| (Z, IN_STROBE, 0x2A), # 27 consume it | |
| (ONE, T2, 0x00), # 2A T2 -= 1; done -> main loop | |
| (Z, Z, 0x24), # 2D next literal byte | |
| (IN_DATA, OUT, 0x33), # 30 REPEAT: emit 0 - value (no strobe) | |
| (ONE, T1, 0x39), # 33 T1 -= 1; done -> 39 | |
| (Z, Z, 0x30), # 36 emit again | |
| (Z, IN_STROBE, 0x3C), # 39 consume the value byte | |
| (Z, Z, 0x00), # 3C goto main loop | |
| ] | |
| def constructor_image() -> List[int]: | |
| mem = [0] * 256 | |
| for idx, (a, b, c) in enumerate(CONSTRUCTOR): | |
| mem[idx * 3] = a | |
| mem[idx * 3 + 1] = b | |
| mem[idx * 3 + 2] = c | |
| mem[ONE] = 1 | |
| return mem | |
| # ============================================================================= | |
| # Backend 1: fast pure-integer reference with devices | |
| # ============================================================================= | |
| def ref_construct(tape: bytes, max_steps: int = 1 << 34, | |
| ) -> Tuple[bytes, int]: | |
| mem = constructor_image() | |
| mem[IN_DATA] = tape[0] if tape else 0 | |
| out = bytearray() | |
| head = 0 | |
| pc = 0 | |
| steps = 0 | |
| tn = len(tape) | |
| while pc != HALT_PC and steps < max_steps: | |
| A = mem[pc] | |
| Bc = mem[(pc + 1) & 0xFF] | |
| C = mem[(pc + 2) & 0xFF] | |
| r = (mem[Bc] - mem[A]) & 0xFF | |
| mem[Bc] = r | |
| pc = C if (r == 0 or r >= 0x80) else (pc + 3) & 0xFF | |
| if Bc == OUT: | |
| out.append(r) | |
| mem[OUT] = 0 | |
| elif Bc == IN_STROBE: | |
| head += 1 | |
| mem[IN_STROBE] = 0 | |
| mem[IN_DATA] = tape[head] if head < tn else 0 | |
| steps += 1 | |
| return bytes(out), steps | |
| # ============================================================================= | |
| # Backend 2: the gate-graph processor (shipped netlist) with devices | |
| # ============================================================================= | |
| class GateHost: | |
| def __init__(self, path: str = MODEL_PATH): | |
| from machines import SubleqThresholdCPU, load_tensors | |
| from eval import load_metadata | |
| T = load_tensors(path) | |
| reg = load_metadata(path)["signal_registry"] | |
| self.cpu = SubleqThresholdCPU(T, reg) | |
| def run(self, tape: bytes, max_steps: int) -> Tuple[bytes, int, dict]: | |
| s = {"pc": 0, "mem": constructor_image(), "halted": False} | |
| s["mem"][IN_DATA] = tape[0] if tape else 0 | |
| out = bytearray() | |
| head = 0 | |
| n = 0 | |
| while not s["halted"] and n < max_steps: | |
| Bc = s["mem"][(s["pc"] + 1) & 0xFF] | |
| s = self.cpu.step(s) | |
| if Bc == OUT: | |
| out.append(s["mem"][OUT]) | |
| s["mem"][OUT] = 0 | |
| elif Bc == IN_STROBE: | |
| head += 1 | |
| s["mem"][IN_STROBE] = 0 | |
| s["mem"][IN_DATA] = tape[head] if head < len(tape) else 0 | |
| n += 1 | |
| return bytes(out), n, s | |
| # ============================================================================= | |
| # Backend 3: the host compiled to recurrent ternary matrices (machine 1 tech) | |
| # ============================================================================= | |
| class MatrixHost: | |
| """The subleq8io processor as a recurrent ternary threshold network, the device applied between steps.""" | |
| N = 8 + 1 + 2048 | |
| def __init__(self, device: str = "cpu"): | |
| t0 = time.perf_counter() | |
| net, inputs, outputs = build_subleq_step_net() | |
| layers, info = compile_net(net, inputs, outputs) | |
| for W, Bt in layers: | |
| vals = set(torch.unique(W).tolist()) | |
| assert vals <= {-1, 0, 1} | |
| self.W = [W.to(device=device, dtype=torch.float32) for W, _ in layers] | |
| self.B = [Bt.to(device=device, dtype=torch.float32) for _, Bt in layers] | |
| self.device = device | |
| self.info = info | |
| print(f" host compiled to {info['layers']} ternary matrices " | |
| f"(max width {info['max_width']}, " | |
| f"{info['total_weights']:,} weights) in " | |
| f"{time.perf_counter() - t0:.1f}s on {device}") | |
| def _vec(self, pc: int, mem: List[int], halt: bool = False) -> torch.Tensor: | |
| v = torch.zeros(self.N) | |
| for k in range(8): | |
| v[k] = (pc >> (7 - k)) & 1 | |
| v[8] = 1.0 if halt else 0.0 | |
| for j in range(256): | |
| for k in range(8): | |
| v[9 + j * 8 + k] = (mem[j] >> (7 - k)) & 1 | |
| return v | |
| def step(self, v: torch.Tensor) -> torch.Tensor: | |
| for W, b in zip(self.W, self.B): | |
| v = ((v @ W.T + b) >= 0).float() | |
| return v | |
| def _byte(vcpu: torch.Tensor, j: int) -> int: | |
| x = 0 | |
| for k in range(8): | |
| x = (x << 1) | int(vcpu[9 + j * 8 + k]) | |
| return x | |
| def run(self, tape: bytes, max_steps: int, expect: Optional[bytes] = None, | |
| report_every: int = 100000) -> Tuple[bytes, int]: | |
| mem = constructor_image() | |
| mem[IN_DATA] = tape[0] if tape else 0 | |
| v = self._vec(0, mem).unsqueeze(0).to(self.device) | |
| out = bytearray() | |
| head = 0 | |
| n = 0 | |
| idx_pch = torch.arange(0, 9, device=self.device) | |
| t0 = time.perf_counter() | |
| while n < max_steps: | |
| front = v[0, idx_pch].tolist() | |
| if front[8] >= 0.5: | |
| break | |
| pcv = 0 | |
| for k in range(8): | |
| pcv = (pcv << 1) | int(front[k]) | |
| bidx = (pcv + 1) & 0xFF | |
| bbits = v[0, 9 + bidx * 8: 9 + bidx * 8 + 8].tolist() | |
| Bc = 0 | |
| for k in range(8): | |
| Bc = (Bc << 1) | int(bbits[k]) | |
| v = self.step(v) | |
| n += 1 | |
| if Bc == OUT: | |
| ob = v[0, 9 + OUT * 8: 9 + OUT * 8 + 8].tolist() | |
| val = 0 | |
| for k in range(8): | |
| val = (val << 1) | int(ob[k]) | |
| out.append(val) | |
| if expect is not None and out[-1] != expect[len(out) - 1]: | |
| raise AssertionError( | |
| f"stream diverged at byte {len(out) - 1}: " | |
| f"got {out[-1]:#04x} want {expect[len(out) - 1]:#04x}") | |
| v[0, 9 + OUT * 8: 9 + OUT * 8 + 8] = 0.0 | |
| elif Bc == IN_STROBE: | |
| head += 1 | |
| v[0, 9 + IN_STROBE * 8: 9 + IN_STROBE * 8 + 8] = 0.0 | |
| nxt = tape[head] if head < len(tape) else 0 | |
| for k in range(8): | |
| v[0, 9 + IN_DATA * 8 + k] = float((nxt >> (7 - k)) & 1) | |
| if report_every and n % report_every == 0: | |
| rate = n / (time.perf_counter() - t0) | |
| print(f" ... {n:,} recurrences, {len(out):,} bytes emitted " | |
| f"({rate:,.0f} instr/s)") | |
| return bytes(out), n | |
| def exhaustive_datapath(self) -> bool: | |
| """All 65,536 (x, y) operand pairs through the matrices in one batch.""" | |
| xs = torch.arange(65536) % 256 | |
| ys = torch.arange(65536) // 256 | |
| V = torch.zeros(65536, self.N) | |
| mem = [0] * 256 | |
| mem[0], mem[1], mem[2] = 0x90, 0x91, 0x30 | |
| v0 = self._vec(0, mem) | |
| V[:] = v0 | |
| for k in range(8): | |
| V[:, 9 + 0x90 * 8 + k] = ((xs >> (7 - k)) & 1).float() | |
| V[:, 9 + 0x91 * 8 + k] = ((ys >> (7 - k)) & 1).float() | |
| V = V.to(self.device) | |
| for W, b in zip(self.W, self.B): | |
| V = ((V @ W.T + b) >= 0).float() | |
| V = V.cpu() | |
| r = torch.zeros(65536, dtype=torch.long) | |
| for k in range(8): | |
| r = (r << 1) | V[:, 9 + 0x91 * 8 + k].long() | |
| pcv = torch.zeros(65536, dtype=torch.long) | |
| for k in range(8): | |
| pcv = (pcv << 1) | V[:, k].long() | |
| exp_r = (ys - xs) & 0xFF | |
| exp_leq = (exp_r == 0) | (exp_r >= 0x80) | |
| exp_pc = torch.where(exp_leq, torch.full_like(pcv, 0x30), | |
| torch.full_like(pcv, 3)) | |
| ok = bool((r == exp_r).all()) and bool((pcv == exp_pc).all()) | |
| print(f" {'OK ' if ok else 'FAIL'} matrix datapath exhaustive: " | |
| f"65,536/65,536 subtract results and branch decisions exact") | |
| return ok | |
| # ============================================================================= | |
| # Verification | |
| # ============================================================================= | |
| def c8_sha(b: bytes) -> str: | |
| return hashlib.sha256(b).hexdigest()[:16] | |
| def family_files() -> List[str]: | |
| files = [os.path.join(REPO, "neural_computer.safetensors")] | |
| vdir = os.path.join(REPO, "variants") | |
| files += sorted(os.path.join(vdir, f) for f in os.listdir(vdir) | |
| if f.endswith(".safetensors")) | |
| return files | |
| def verify(device: str) -> bool: | |
| ok = True | |
| print("\n[1/5] Host machine soundness (shipped netlist, exhaustive datapath)") | |
| from eval import NetlistEvaluator, load_metadata | |
| from machines import load_tensors | |
| T = load_tensors(MODEL_PATH) | |
| reg = load_metadata(MODEL_PATH)["signal_registry"] | |
| ne = NetlistEvaluator(T, reg, "subleq") | |
| a_v = torch.arange(65536) % 256 | |
| b_v = torch.arange(65536) // 256 | |
| ext = {} | |
| for k in range(8): | |
| ext[f"$a[{k}]"] = ((a_v >> k) & 1).float() | |
| ext[f"$b[{k}]"] = ((b_v >> k) & 1).float() | |
| ext[f"$pc[{k}]"] = torch.zeros(65536) | |
| ext[f"$c[{k}]"] = torch.zeros(65536) | |
| o = ne.run(ext) | |
| rr = sum(o[f"subleq.sub.fa{k}.ha2.sum.layer2"][:, 0].long() << k for k in range(8)) | |
| lq = o["subleq.leq"][:, 0].long() | |
| exp_r = (b_v - a_v) & 0xFF | |
| exp_l = ((exp_r == 0) | (exp_r >= 128)).long() | |
| good = bool((rr == exp_r).all()) and bool((lq == exp_l).all()) | |
| print(f" {'OK ' if good else 'FAIL'} gate netlist exhaustive: 65,536/65,536 exact") | |
| ok &= good | |
| print("\n[2/5] Recipe codec round-trip over the whole family") | |
| total = 0 | |
| for p in family_files(): | |
| data = open(p, "rb").read() | |
| tape = c8_describe(data) | |
| good = c8_decode(tape) == data | |
| ok &= good | |
| total += len(data) | |
| print(f" {'OK ' if good else 'FAIL'} {os.path.basename(p):<44} " | |
| f"{len(data):>10,} B -> tape {len(tape):>10,} B sha {c8_sha(data)}") | |
| print(f" ({total / 1e6:.0f} MB described and decoded back, byte-identical)") | |
| print("\n[3/5] Constructor program on the integer reference " | |
| "(output stream == target file, byte for byte)") | |
| targets = [MODEL_PATH, | |
| os.path.join(REPO, "variants", "neural_subleq8.safetensors"), | |
| os.path.join(REPO, "variants", "neural_matrix8.safetensors")] | |
| for p in targets: | |
| data = open(p, "rb").read() | |
| tape = c8_describe(data) | |
| t0 = time.perf_counter() | |
| out, steps = ref_construct(tape) | |
| dt = time.perf_counter() - t0 | |
| good = out == data | |
| ok &= good | |
| tag = "SELF-REPRODUCTION" if p == MODEL_PATH else "universal build" | |
| print(f" {'OK ' if good else 'FAIL'} {os.path.basename(p):<40} " | |
| f"{len(out):>10,} B in {steps:>12,} instructions ({dt:.1f}s) " | |
| f"[{tag}] sha {c8_sha(out)}") | |
| print("\n[4/5] Matrix backend (machine-1 compile of the host)") | |
| mh = MatrixHost(device=device) | |
| ok &= mh.exhaustive_datapath() | |
| print("\n[5/5] Backend lockstep on a shared prefix " | |
| "(gate graph vs matrices vs reference, first 600 cycles)") | |
| data = open(MODEL_PATH, "rb").read() | |
| tape = c8_describe(data) | |
| gh = GateHost() | |
| s = {"pc": 0, "mem": constructor_image(), "halted": False} | |
| s["mem"][IN_DATA] = tape[0] | |
| mem2 = constructor_image() | |
| mem2[IN_DATA] = tape[0] | |
| v = mh._vec(0, mem2).unsqueeze(0).to(device) | |
| headg = headm = 0 | |
| outg, outm = bytearray(), bytearray() | |
| good = True | |
| for n in range(600): | |
| Bg = s["mem"][(s["pc"] + 1) & 0xFF] | |
| s = gh.cpu.step(s) | |
| if Bg == OUT: | |
| outg.append(s["mem"][OUT]) | |
| s["mem"][OUT] = 0 | |
| elif Bg == IN_STROBE: | |
| headg += 1 | |
| s["mem"][IN_STROBE] = 0 | |
| s["mem"][IN_DATA] = tape[headg] if headg < len(tape) else 0 | |
| vc = v[0].cpu() | |
| pcm = 0 | |
| for k in range(8): | |
| pcm = (pcm << 1) | int(vc[k]) | |
| Bm = MatrixHost._byte(vc, (pcm + 1) & 0xFF) | |
| v = mh.step(v) | |
| if Bm == OUT: | |
| outm.append(MatrixHost._byte(v[0].cpu(), OUT)) | |
| v[0, 9 + OUT * 8: 9 + OUT * 8 + 8] = 0.0 | |
| elif Bm == IN_STROBE: | |
| headm += 1 | |
| v[0, 9 + IN_STROBE * 8: 9 + IN_STROBE * 8 + 8] = 0.0 | |
| nxt = tape[headm] if headm < len(tape) else 0 | |
| for k in range(8): | |
| v[0, 9 + IN_DATA * 8 + k] = float((nxt >> (7 - k)) & 1) | |
| vc = v[0].cpu() | |
| pcm = 0 | |
| for k in range(8): | |
| pcm = (pcm << 1) | int(vc[k]) | |
| memm = [MatrixHost._byte(vc, j) for j in range(256)] | |
| if pcm != s["pc"] or memm != s["mem"] or outg != outm: | |
| print(f" FAIL lockstep diverged at cycle {n}") | |
| good = False | |
| break | |
| print(f" {'OK ' if good else 'FAIL'} gate graph and matrix form agree " | |
| f"cycle-for-cycle for 600 cycles ({len(outg)} bytes emitted identically)") | |
| ok &= good | |
| print("\nCONSTRUCTOR VERIFICATION:", "PASS" if ok else "FAIL") | |
| return ok | |
| def self_reproduce(device: str, gen2: bool = True) -> bool: | |
| """Full self-reproduction at threshold fidelity on the matrix backend.""" | |
| data = open(MODEL_PATH, "rb").read() | |
| tape = c8_describe(data) | |
| print(f"\nSelf-reproduction on the threshold matrices: target " | |
| f"{len(data):,} bytes (sha {c8_sha(data)}), tape {len(tape):,} bytes") | |
| ref_out, ref_steps = ref_construct(tape) | |
| assert ref_out == data, "reference construction failed" | |
| print(f" reference: {ref_steps:,} instructions") | |
| mh = MatrixHost(device=device) | |
| t0 = time.perf_counter() | |
| out, n = mh.run(tape, max_steps=ref_steps + 64, expect=data) | |
| dt = time.perf_counter() - t0 | |
| ok = out == data and n == ref_steps | |
| print(f" GEN 1: emitted {len(out):,} bytes in {n:,} recurrences " | |
| f"({dt / 60:.1f} min, {n / dt:,.0f} instr/s) sha {c8_sha(out)}") | |
| print(f" {'OK ' if ok else 'FAIL'} output stream == host safetensors file, " | |
| f"byte for byte; instruction count matches reference exactly") | |
| if ok and gen2: | |
| off_path = os.path.join(REPO, "variants", "_offspring_subleq8io.safetensors") | |
| with open(off_path, "wb") as f: | |
| f.write(out) | |
| gh = GateHost(off_path) # boot the offspring's own gates | |
| probe_tape = c8_describe(out[:2048]) | |
| want = out[:2048] | |
| got, steps, _ = gh.run(probe_tape, max_steps=len(probe_tape) * 8 + 64) | |
| good2 = got == want | |
| print(f" GEN 2: offspring file booted as a machine (gate graph); " | |
| f"constructed the first {len(want):,} bytes of itself in " | |
| f"{steps:,} instructions: {'exact' if good2 else 'MISMATCH'}") | |
| os.remove(off_path) | |
| ok &= good2 | |
| print("SELF-REPRODUCTION:", "PASS" if ok else "FAIL") | |
| return ok | |
| def main_constructor8() -> int: | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("cmd", choices=["build", "verify", "self", "all"]) | |
| ap.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu") | |
| args = ap.parse_args() | |
| rc = 0 | |
| if args.cmd in ("build", "all"): | |
| build_host(save=True) | |
| if args.cmd in ("verify", "all"): | |
| rc |= 0 if verify(args.device) else 1 | |
| if args.cmd in ("self", "all"): | |
| rc |= 0 if self_reproduce(args.device) else 1 | |
| return rc | |
| def cmd_family(): | |
| import hashlib | |
| import time | |
| for A in [int(a) for a in sys.argv[1:]] or [3, 4, 5, 8]: | |
| t0 = time.perf_counter() | |
| s = sigma(A) | |
| t1 = time.perf_counter() | |
| line = f"A={A}: sigma {len(s):,} bytes in {t1 - t0:.2f}s, sha256 {hashlib.sha256(s).hexdigest()[:16]}" | |
| if A <= 12: | |
| net, inp, out = build(A) | |
| same = family_sigma_of_net(net, inp, out, A) == s | |
| depth = {} | |
| for gname, (ins, _) in net.gates.items(): | |
| depth[gname] = 1 + max((depth.get(x, 0) for x, _ in ins), default=0) | |
| line += (f"; Net {len(net.gates):,} units, depth {max(depth.values())}, " | |
| f"edges {sum(len(i) for i, _ in net.gates.values()):,}; " | |
| f"equal to the Net's serialization: {same}") | |
| N2, i2, o2, _ = net_of_sigma(s) | |
| line += f"; parses to {len(N2.gates):,} units" | |
| print(line, flush=True) | |
| COMMANDS = {'family': cmd_family, 'constructor8': main_constructor8} | |
| if __name__ == "__main__": | |
| if len(sys.argv) < 2 or sys.argv[1] not in COMMANDS: | |
| sys.exit('commands: ' + ', '.join(COMMANDS)) | |
| cmd = sys.argv.pop(1) | |
| sys.argv[0] += ' ' + cmd | |
| sys.exit(COMMANDS[cmd]()) | |