CharlesCNorton commited on
Commit
79eda78
·
1 Parent(s): 83f5598

neural_reversible: analog read-noise and conductance-mismatch sweeps on the reversible matrix stack. The permutation stays bit-exact through read noise sigma ~ 0.10 (errors at 0.15, where the 0.5-margin error model predicts) and conductance mismatch sigma_G ~ 0.10, the same tolerances neural_matrix8 measures; README states the confirmed tolerance rather than implying it.

Browse files
Files changed (2) hide show
  1. README.md +3 -1
  2. tools/reversible_matrix.py +41 -17
README.md CHANGED
@@ -643,7 +643,9 @@ matrix stack `neural_matrix8` uses: the 4-bit adder becomes 39 ternary matrices
643
  with a Heaviside step, its composed transition is a verified permutation of the
644
  state space, and every pre-activation clears the analog threshold by the same
645
  0.5 margin, so the crossbar realization is bit-exact and information-theoretically
646
- lossless. Turning that into measured energy below the Landauer bound requires
 
 
647
  adiabatic drive of the crossbar, which is the physical frontier; the logical
648
  reversibility and its lossless matrix realization are proven here.
649
 
 
643
  with a Heaviside step, its composed transition is a verified permutation of the
644
  state space, and every pre-activation clears the analog threshold by the same
645
  0.5 margin, so the crossbar realization is bit-exact and information-theoretically
646
+ lossless, holding under injected read noise through sigma ~ 0.10 and static
647
+ conductance mismatch through sigma_G ~ 0.10, the same tolerances `neural_matrix8`
648
+ measures. Turning that into measured energy below the Landauer bound requires
649
  adiabatic drive of the crossbar, which is the physical frontier; the logical
650
  reversibility and its lossless matrix realization are proven here.
651
 
tools/reversible_matrix.py CHANGED
@@ -40,29 +40,52 @@ def adder_net(width: int):
40
  return net, inputs, list(cur), n, a_bits, b_bits, carry
41
 
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  def main() -> int:
44
  width = 4
45
  net, inputs, outputs, n, a_bits, b_bits, carry = adder_net(width)
46
  layers, info = compile_net(net, inputs, outputs)
47
  mm = MatrixMachine(layers)
48
 
49
- seen = set()
50
- bad = 0
51
- vecs = []
52
- for x in range(1 << n):
53
- reg = [(x >> i) & 1 for i in range(n)]
54
- v = torch.tensor([[float(b) for b in reg]])
55
- out = mm.step(v)[0]
56
- y = sum(int(out[i].item()) << i for i in range(n))
57
- seen.add(y)
58
- ref = list(reg)
59
- rv._apply(ref, rv._adder_ops(a_bits, b_bits, carry))
60
- if y != sum(ref[i] << i for i in range(n)):
61
- bad += 1
62
- vecs.append(v[0])
63
-
64
- perm = len(seen) == (1 << n)
65
- margin = mm.min_margin(torch.stack(vecs[:256]))
66
  print(f"reversible {width}-bit adder as a ternary matrix stack")
67
  print(f" layers={info['layers']} gates={info['gates']} "
68
  f"max_width={info['max_width']} total_weights={info['total_weights']}")
@@ -70,6 +93,7 @@ def main() -> int:
70
  print(f" matches the gate circuit over all {1 << n} inputs: {'OK' if bad == 0 else f'FAIL({bad})'}")
71
  print(f" composed transition is a permutation of the state space: {'OK' if perm else 'FAIL'}")
72
  print(f" analog noise margin, all layers: {margin:.3f} (guarantee 0.5)")
 
73
  ok = bad == 0 and perm and abs(margin - 0.5) < 1e-6
74
  print("PASS" if ok else "FAIL")
75
  return 0 if ok else 1
 
40
  return net, inputs, list(cur), n, a_bits, b_bits, carry
41
 
42
 
43
+ def _refs(n, a_bits, b_bits, carry):
44
+ out = []
45
+ for x in range(1 << n):
46
+ reg = [(x >> i) & 1 for i in range(n)]
47
+ rv._apply(reg, rv._adder_ops(a_bits, b_bits, carry))
48
+ out.append(sum(reg[i] << i for i in range(n)))
49
+ return torch.tensor(out)
50
+
51
+
52
+ def _outputs(mm, states, n, **step_kw):
53
+ v = mm.step(states.clone(), **step_kw)
54
+ bits = (v >= 0.5).to(torch.int64)
55
+ weights = torch.tensor([1 << i for i in range(n)])
56
+ return (bits * weights).sum(dim=1)
57
+
58
+
59
+ def analog_sweep(mm, states, refs, n):
60
+ """The permutation must survive analog imperfection. Read noise is injected
61
+ per matrix-vector product; conductance mismatch is a fixed per-device
62
+ perturbation of the ternary weights."""
63
+ print(" read-noise sweep (bit-exact = all 512 outputs still match, 4 trials):")
64
+ for sigma in (0.05, 0.10, 0.15, 0.20):
65
+ ok = all((_outputs(mm, states, n, analog=True, noise_sigma=sigma,
66
+ gen=torch.Generator().manual_seed(s)) == refs).all()
67
+ for s in range(4))
68
+ print(f" sigma={sigma:.2f}: {'bit-exact' if ok else 'errors appear'}")
69
+ print(" conductance-mismatch sweep (fixed per-device weight perturbation):")
70
+ for sg in (0.05, 0.10, 0.15):
71
+ ok = (_outputs(mm.perturbed(sg, seed=0), states, n, analog=True) == refs).all()
72
+ print(f" sigma_G={sg:.2f}: {'bit-exact' if ok else 'errors appear'}")
73
+
74
+
75
  def main() -> int:
76
  width = 4
77
  net, inputs, outputs, n, a_bits, b_bits, carry = adder_net(width)
78
  layers, info = compile_net(net, inputs, outputs)
79
  mm = MatrixMachine(layers)
80
 
81
+ states = torch.stack([torch.tensor([float((x >> i) & 1) for i in range(n)])
82
+ for x in range(1 << n)])
83
+ refs = _refs(n, a_bits, b_bits, carry)
84
+ got = _outputs(mm, states, n)
85
+ bad = int((got != refs).sum())
86
+ perm = len(set(got.tolist())) == (1 << n)
87
+ margin = mm.min_margin(states[:256])
88
+
 
 
 
 
 
 
 
 
 
89
  print(f"reversible {width}-bit adder as a ternary matrix stack")
90
  print(f" layers={info['layers']} gates={info['gates']} "
91
  f"max_width={info['max_width']} total_weights={info['total_weights']}")
 
93
  print(f" matches the gate circuit over all {1 << n} inputs: {'OK' if bad == 0 else f'FAIL({bad})'}")
94
  print(f" composed transition is a permutation of the state space: {'OK' if perm else 'FAIL'}")
95
  print(f" analog noise margin, all layers: {margin:.3f} (guarantee 0.5)")
96
+ analog_sweep(mm, states, refs, n)
97
  ok = bad == 0 and perm and abs(margin - 0.5) < 1e-6
98
  print("PASS" if ok else "FAIL")
99
  return 0 if ok else 1