Fix broken circuits: bitwise shifts, symmetry8bit, subtractor eval
Browse filesCIRCUIT FIXES (convert_to_explicit_inputs.py):
1. BITWISE SHIFT CIRCUITS (asr8bit, rol8bit, ror8bit):
- Old weights were all [1.0] selecting only x[0] for every output
- Added build_bitwise_shift_tensors() with correct weights:
* ASR: bit[i] = x[i+1] for i<7, bit[7] = x[7] (sign extension)
* ROL: bit[0] = x[7], bit[i] = x[i-1] for i>0
* ROR: bit[i] = x[i+1] for i<7, bit[7] = x[0]
- Each output gate now has proper weight vector to select correct input bit
2. SYMMETRY8BIT CIRCUIT:
- Old XNOR gates had weights [1,1] with no bias (acted as OR, not XNOR)
- Added build_symmetry8bit_tensors() with proper 2-layer XNOR:
* Layer 1: AND gate (weight selects pair, bias -1.5 requires both)
* Layer 1: NOR gate (negative weights, bias 0 fires when both 0)
* Layer 2: OR of AND and NOR outputs (fires when a==b)
- Final AND gate combines 4 XNOR results (bias -3.5 requires all 4)
3. SUBTRACTOR EVAL FIX (eval.py):
- eval_subtractor() now properly handles internal NOT gates
- Circuit has notb0-notb7 gates that invert b internally
- Eval now: (1) evaluates notb gates, (2) passes inverted b to full adders
- Fixes arithmetic.sub8bit: 0/65536 -> 65536/65536 PASS
MAIN() UPDATES:
- Remove old broken asr8bit/rol8bit/ror8bit tensors before rebuild
- Remove old broken symmetry8bit tensors before rebuild
- Add build_bitwise_shift_tensors() call (54 tensors)
- Add build_symmetry8bit_tensors() call (26 tensors)
EVAL.PY UPDATES:
- Updated symmetry8bit test to use new 2-layer XNOR structure
- Test now evaluates: layer1.and, layer1.nor -> layer2 for each XNOR
RESULTS:
Before: 67.65% (139,452/206,124) - 6 circuits failing
After: 99.97% (206,057/206,124) - 1 circuit failing
PASSING NOW:
- arithmetic.sub8bit: 65536/65536 ✓
- arithmetic.asr8bit: 256/256 ✓
- arithmetic.rol8bit: 256/256 ✓
- arithmetic.ror8bit: 256/256 ✓
- pattern_recognition.symmetry8bit: 256/256 ✓
STILL FAILING:
- arithmetic.sbc8bit: 67/134 (test logic issue, not circuit)
- arithmetic.safetensors +2 -2
- convert_to_explicit_inputs.py +142 -0
- eval.py +25 -9
|
@@ -1,3 +1,3 @@
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
-
oid sha256:
|
| 3 |
-
size
|
|
|
|
| 1 |
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:cd147a9a27df47020615265d1c0a62b10cd4839d2a6252b1f19c8c2dbf83790d
|
| 3 |
+
size 3062104
|
|
@@ -6635,6 +6635,127 @@ def build_modular_power2_tensors() -> Dict[str, torch.Tensor]:
|
|
| 6635 |
return tensors
|
| 6636 |
|
| 6637 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6638 |
def build_clz8bit_tensors() -> Dict[str, torch.Tensor]:
|
| 6639 |
"""Build tensors for arithmetic.clz8bit circuit.
|
| 6640 |
|
|
@@ -6722,6 +6843,19 @@ def main():
|
|
| 6722 |
del tensors[k]
|
| 6723 |
print(f"Removed {len(old_mod_power2)} old mod2/mod4/mod8 tensors")
|
| 6724 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6725 |
# Build new circuits
|
| 6726 |
print("Building new circuits...")
|
| 6727 |
clz_tensors = build_clz8bit_tensors()
|
|
@@ -6784,6 +6918,14 @@ def main():
|
|
| 6784 |
tensors.update(mod_power2_tensors)
|
| 6785 |
print(f" modular.mod2/4/8: {len(mod_power2_tensors)} tensors")
|
| 6786 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6787 |
print(f"Total tensors: {len(tensors)}")
|
| 6788 |
|
| 6789 |
# Load routing for complex circuits
|
|
|
|
| 6635 |
return tensors
|
| 6636 |
|
| 6637 |
|
| 6638 |
+
def build_bitwise_shift_tensors() -> Dict[str, torch.Tensor]:
|
| 6639 |
+
"""Build tensors for arithmetic.asr8bit, rol8bit, ror8bit circuits.
|
| 6640 |
+
|
| 6641 |
+
ASR (Arithmetic Shift Right by 1):
|
| 6642 |
+
- bit[i] = x[i+1] for i in 0..6
|
| 6643 |
+
- bit[7] = x[7] (sign extension)
|
| 6644 |
+
- shiftout = x[0]
|
| 6645 |
+
|
| 6646 |
+
ROL (Rotate Left by 1):
|
| 6647 |
+
- bit[0] = x[7] (wrap around)
|
| 6648 |
+
- bit[i] = x[i-1] for i in 1..7
|
| 6649 |
+
- cout = x[7]
|
| 6650 |
+
|
| 6651 |
+
ROR (Rotate Right by 1):
|
| 6652 |
+
- bit[i] = x[i+1] for i in 0..6
|
| 6653 |
+
- bit[7] = x[0] (wrap around)
|
| 6654 |
+
- cout = x[0]
|
| 6655 |
+
"""
|
| 6656 |
+
tensors = {}
|
| 6657 |
+
|
| 6658 |
+
# ASR8BIT - Arithmetic Shift Right by 1
|
| 6659 |
+
prefix = "arithmetic.asr8bit"
|
| 6660 |
+
for i in range(8):
|
| 6661 |
+
if i < 7:
|
| 6662 |
+
# bit[i] gets x[i+1] (shift right)
|
| 6663 |
+
w = [0.0] * 8
|
| 6664 |
+
w[i + 1] = 1.0
|
| 6665 |
+
else:
|
| 6666 |
+
# bit[7] gets x[7] (sign extension)
|
| 6667 |
+
w = [0.0] * 8
|
| 6668 |
+
w[7] = 1.0
|
| 6669 |
+
tensors[f"{prefix}.bit{i}.weight"] = torch.tensor(w)
|
| 6670 |
+
tensors[f"{prefix}.bit{i}.bias"] = torch.tensor([-0.5])
|
| 6671 |
+
# shiftout gets x[0]
|
| 6672 |
+
w = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
| 6673 |
+
tensors[f"{prefix}.shiftout.weight"] = torch.tensor(w)
|
| 6674 |
+
tensors[f"{prefix}.shiftout.bias"] = torch.tensor([-0.5])
|
| 6675 |
+
|
| 6676 |
+
# ROL8BIT - Rotate Left by 1
|
| 6677 |
+
prefix = "arithmetic.rol8bit"
|
| 6678 |
+
for i in range(8):
|
| 6679 |
+
w = [0.0] * 8
|
| 6680 |
+
if i == 0:
|
| 6681 |
+
# bit[0] gets x[7] (wrap around)
|
| 6682 |
+
w[7] = 1.0
|
| 6683 |
+
else:
|
| 6684 |
+
# bit[i] gets x[i-1]
|
| 6685 |
+
w[i - 1] = 1.0
|
| 6686 |
+
tensors[f"{prefix}.bit{i}.weight"] = torch.tensor(w)
|
| 6687 |
+
tensors[f"{prefix}.bit{i}.bias"] = torch.tensor([-0.5])
|
| 6688 |
+
# cout gets x[7]
|
| 6689 |
+
w = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0]
|
| 6690 |
+
tensors[f"{prefix}.cout.weight"] = torch.tensor(w)
|
| 6691 |
+
tensors[f"{prefix}.cout.bias"] = torch.tensor([-0.5])
|
| 6692 |
+
|
| 6693 |
+
# ROR8BIT - Rotate Right by 1
|
| 6694 |
+
prefix = "arithmetic.ror8bit"
|
| 6695 |
+
for i in range(8):
|
| 6696 |
+
w = [0.0] * 8
|
| 6697 |
+
if i < 7:
|
| 6698 |
+
# bit[i] gets x[i+1]
|
| 6699 |
+
w[i + 1] = 1.0
|
| 6700 |
+
else:
|
| 6701 |
+
# bit[7] gets x[0] (wrap around)
|
| 6702 |
+
w[0] = 1.0
|
| 6703 |
+
tensors[f"{prefix}.bit{i}.weight"] = torch.tensor(w)
|
| 6704 |
+
tensors[f"{prefix}.bit{i}.bias"] = torch.tensor([-0.5])
|
| 6705 |
+
# cout gets x[0]
|
| 6706 |
+
w = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
|
| 6707 |
+
tensors[f"{prefix}.cout.weight"] = torch.tensor(w)
|
| 6708 |
+
tensors[f"{prefix}.cout.bias"] = torch.tensor([-0.5])
|
| 6709 |
+
|
| 6710 |
+
return tensors
|
| 6711 |
+
|
| 6712 |
+
|
| 6713 |
+
def build_symmetry8bit_tensors() -> Dict[str, torch.Tensor]:
|
| 6714 |
+
"""Build tensors for pattern_recognition.symmetry8bit circuit.
|
| 6715 |
+
|
| 6716 |
+
Checks if an 8-bit input is a palindrome (symmetric).
|
| 6717 |
+
bit[0] == bit[7], bit[1] == bit[6], bit[2] == bit[5], bit[3] == bit[4]
|
| 6718 |
+
|
| 6719 |
+
XNOR as threshold gate: XNOR(a,b) = 1 if a==b
|
| 6720 |
+
This requires a 2-layer structure per XNOR:
|
| 6721 |
+
- Layer 1: AND(a,b) and NOR(a,b)
|
| 6722 |
+
- Layer 2: OR of AND and NOR outputs
|
| 6723 |
+
|
| 6724 |
+
Then final AND of all 4 XNOR results.
|
| 6725 |
+
"""
|
| 6726 |
+
tensors = {}
|
| 6727 |
+
prefix = "pattern_recognition.symmetry8bit"
|
| 6728 |
+
|
| 6729 |
+
# XNOR gates for comparing bit pairs: (0,7), (1,6), (2,5), (3,4)
|
| 6730 |
+
pairs = [(0, 7), (1, 6), (2, 5), (3, 4)]
|
| 6731 |
+
|
| 6732 |
+
for i, (lo, hi) in enumerate(pairs):
|
| 6733 |
+
# Layer 1: AND(a,b) - fires when both are 1
|
| 6734 |
+
# Weight: select bits lo and hi from 8-bit input
|
| 6735 |
+
w_and = [0.0] * 8
|
| 6736 |
+
w_and[lo] = 1.0
|
| 6737 |
+
w_and[hi] = 1.0
|
| 6738 |
+
tensors[f"{prefix}.xnor{i}.layer1.and.weight"] = torch.tensor(w_and)
|
| 6739 |
+
tensors[f"{prefix}.xnor{i}.layer1.and.bias"] = torch.tensor([-1.5]) # Need both
|
| 6740 |
+
|
| 6741 |
+
# Layer 1: NOR(a,b) - fires when both are 0
|
| 6742 |
+
w_nor = [0.0] * 8
|
| 6743 |
+
w_nor[lo] = -1.0
|
| 6744 |
+
w_nor[hi] = -1.0
|
| 6745 |
+
tensors[f"{prefix}.xnor{i}.layer1.nor.weight"] = torch.tensor(w_nor)
|
| 6746 |
+
tensors[f"{prefix}.xnor{i}.layer1.nor.bias"] = torch.tensor([0.0]) # Fire when sum < 0
|
| 6747 |
+
|
| 6748 |
+
# Layer 2: OR of AND and NOR - fires when either is 1 (i.e., a==b)
|
| 6749 |
+
tensors[f"{prefix}.xnor{i}.layer2.weight"] = torch.tensor([1.0, 1.0])
|
| 6750 |
+
tensors[f"{prefix}.xnor{i}.layer2.bias"] = torch.tensor([-0.5])
|
| 6751 |
+
|
| 6752 |
+
# Final AND of all 4 XNOR results
|
| 6753 |
+
tensors[f"{prefix}.and.weight"] = torch.tensor([1.0, 1.0, 1.0, 1.0])
|
| 6754 |
+
tensors[f"{prefix}.and.bias"] = torch.tensor([-3.5]) # Need all 4
|
| 6755 |
+
|
| 6756 |
+
return tensors
|
| 6757 |
+
|
| 6758 |
+
|
| 6759 |
def build_clz8bit_tensors() -> Dict[str, torch.Tensor]:
|
| 6760 |
"""Build tensors for arithmetic.clz8bit circuit.
|
| 6761 |
|
|
|
|
| 6843 |
del tensors[k]
|
| 6844 |
print(f"Removed {len(old_mod_power2)} old mod2/mod4/mod8 tensors")
|
| 6845 |
|
| 6846 |
+
# Remove broken bitwise shift tensors
|
| 6847 |
+
old_bitwise = [k for k in tensors.keys() if k.startswith('arithmetic.asr8bit') or
|
| 6848 |
+
k.startswith('arithmetic.rol8bit') or k.startswith('arithmetic.ror8bit')]
|
| 6849 |
+
for k in old_bitwise:
|
| 6850 |
+
del tensors[k]
|
| 6851 |
+
print(f"Removed {len(old_bitwise)} old asr8bit/rol8bit/ror8bit tensors")
|
| 6852 |
+
|
| 6853 |
+
# Remove broken symmetry8bit tensors
|
| 6854 |
+
old_symmetry = [k for k in tensors.keys() if k.startswith('pattern_recognition.symmetry8bit')]
|
| 6855 |
+
for k in old_symmetry:
|
| 6856 |
+
del tensors[k]
|
| 6857 |
+
print(f"Removed {len(old_symmetry)} old symmetry8bit tensors")
|
| 6858 |
+
|
| 6859 |
# Build new circuits
|
| 6860 |
print("Building new circuits...")
|
| 6861 |
clz_tensors = build_clz8bit_tensors()
|
|
|
|
| 6918 |
tensors.update(mod_power2_tensors)
|
| 6919 |
print(f" modular.mod2/4/8: {len(mod_power2_tensors)} tensors")
|
| 6920 |
|
| 6921 |
+
bitwise_tensors = build_bitwise_shift_tensors()
|
| 6922 |
+
tensors.update(bitwise_tensors)
|
| 6923 |
+
print(f" bitwise shifts: {len(bitwise_tensors)} tensors")
|
| 6924 |
+
|
| 6925 |
+
symmetry_tensors = build_symmetry8bit_tensors()
|
| 6926 |
+
tensors.update(symmetry_tensors)
|
| 6927 |
+
print(f" symmetry8bit: {len(symmetry_tensors)} tensors")
|
| 6928 |
+
|
| 6929 |
print(f"Total tensors: {len(tensors)}")
|
| 6930 |
|
| 6931 |
# Load routing for complex circuits
|
|
@@ -663,7 +663,11 @@ def test_clz(ctx: EvalContext) -> List[TestResult]:
|
|
| 663 |
|
| 664 |
def eval_subtractor(ctx: EvalContext, prefix: str, a_bits: List[float],
|
| 665 |
b_bits: List[float]) -> Tuple[List[float], float]:
|
| 666 |
-
"""Evaluate 8-bit subtractor (a - b) using full adders with b inverted + carry-in=1.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 667 |
n = len(a_bits)
|
| 668 |
result = []
|
| 669 |
|
|
@@ -673,10 +677,18 @@ def eval_subtractor(ctx: EvalContext, prefix: str, a_bits: List[float],
|
|
| 673 |
else:
|
| 674 |
carry = 1.0
|
| 675 |
|
|
|
|
|
|
|
| 676 |
for i in range(n):
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 680 |
result.append(sum_bit)
|
| 681 |
|
| 682 |
return result, carry
|
|
@@ -1403,19 +1415,23 @@ def test_pattern_recognition(ctx: EvalContext) -> List[TestResult]:
|
|
| 1403 |
results.append(TestResult("pattern_recognition.alternating8bit", 2, 2))
|
| 1404 |
|
| 1405 |
# Symmetry - checks if bit pattern is a palindrome
|
| 1406 |
-
|
|
|
|
| 1407 |
passed, total = 0, 0
|
| 1408 |
test_range = range(256) if not ctx.quick else range(0, 256, 16)
|
| 1409 |
|
| 1410 |
for val in test_range:
|
| 1411 |
bits = [float((val >> i) & 1) for i in range(8)]
|
| 1412 |
|
| 1413 |
-
# Evaluate XNOR for each pair:
|
| 1414 |
xnor_results = []
|
| 1415 |
for i in range(4):
|
| 1416 |
-
|
| 1417 |
-
|
| 1418 |
-
|
|
|
|
|
|
|
|
|
|
| 1419 |
xnor_results.append(xnor_val)
|
| 1420 |
|
| 1421 |
# Final AND of all XNOR results
|
|
|
|
| 663 |
|
| 664 |
def eval_subtractor(ctx: EvalContext, prefix: str, a_bits: List[float],
|
| 665 |
b_bits: List[float]) -> Tuple[List[float], float]:
|
| 666 |
+
"""Evaluate 8-bit subtractor (a - b) using full adders with b inverted + carry-in=1.
|
| 667 |
+
|
| 668 |
+
The subtractor circuit has internal NOT gates (notb0-notb7) that invert b,
|
| 669 |
+
then uses full adders to compute a + ~b + 1.
|
| 670 |
+
"""
|
| 671 |
n = len(a_bits)
|
| 672 |
result = []
|
| 673 |
|
|
|
|
| 677 |
else:
|
| 678 |
carry = 1.0
|
| 679 |
|
| 680 |
+
# First, invert b bits using the circuit's NOT gates
|
| 681 |
+
notb_bits = []
|
| 682 |
for i in range(n):
|
| 683 |
+
if f"{prefix}.notb{i}.weight" in ctx.tensors:
|
| 684 |
+
notb = eval_gate_direct(ctx, f"{prefix}.notb{i}", [b_bits[i]])
|
| 685 |
+
else:
|
| 686 |
+
notb = 1.0 - b_bits[i] # Manual NOT
|
| 687 |
+
notb_bits.append(notb)
|
| 688 |
+
|
| 689 |
+
# Now evaluate full adders with a and inverted b
|
| 690 |
+
for i in range(n):
|
| 691 |
+
sum_bit, carry = eval_full_adder(ctx, f"{prefix}.fa{i}", a_bits[i], notb_bits[i], carry)
|
| 692 |
result.append(sum_bit)
|
| 693 |
|
| 694 |
return result, carry
|
|
|
|
| 1415 |
results.append(TestResult("pattern_recognition.alternating8bit", 2, 2))
|
| 1416 |
|
| 1417 |
# Symmetry - checks if bit pattern is a palindrome
|
| 1418 |
+
# Uses 2-layer XNOR structure: layer1.and + layer1.nor -> layer2
|
| 1419 |
+
if f"pattern_recognition.symmetry8bit.xnor0.layer1.and.weight" in ctx.tensors:
|
| 1420 |
passed, total = 0, 0
|
| 1421 |
test_range = range(256) if not ctx.quick else range(0, 256, 16)
|
| 1422 |
|
| 1423 |
for val in test_range:
|
| 1424 |
bits = [float((val >> i) & 1) for i in range(8)]
|
| 1425 |
|
| 1426 |
+
# Evaluate XNOR for each pair: (0,7), (1,6), (2,5), (3,4)
|
| 1427 |
xnor_results = []
|
| 1428 |
for i in range(4):
|
| 1429 |
+
prefix = f"pattern_recognition.symmetry8bit.xnor{i}"
|
| 1430 |
+
# Layer 1: AND and NOR take all 8 bits (weights select the pair)
|
| 1431 |
+
and_val = eval_gate_direct(ctx, f"{prefix}.layer1.and", bits)
|
| 1432 |
+
nor_val = eval_gate_direct(ctx, f"{prefix}.layer1.nor", bits)
|
| 1433 |
+
# Layer 2: OR of AND and NOR
|
| 1434 |
+
xnor_val = eval_gate_direct(ctx, f"{prefix}.layer2", [and_val, nor_val])
|
| 1435 |
xnor_results.append(xnor_val)
|
| 1436 |
|
| 1437 |
# Final AND of all XNOR results
|