Always regenerate .inputs from a fresh signal registry
Browse filesPreviously cmd_inputs only deleted-and-rebuilt .inputs tensors when
their length disagreed with the gate's .weight. That caught the
bit-cascade migration but missed the more common case: .inputs has
the right length but its signal IDs reference a stale registry from
an earlier build (signal IDs are session-local; a different cmd_inputs
run produces different IDs for the same logical signal).
build_inputs now clears every single-gate .inputs up front and
regenerates from a fresh SignalRegistry. Packed multi-gate tensors
keep their .inputs untouched because they use a different convention.
build_all.py: after quantize.py --ternary, re-invoke `build.py inputs`
so that the new modular detector gates introduced by ternarization
get routing metadata. Without this, 2,879 modular.mod{N}.eq.k{k}.*
gates per variant had no .inputs at all.
Verified on neural_alu8: gates without .inputs went from 2,879 to 0.
The remaining mismatched-length .inputs (~3,400) are in seed-file
gate families (expr_paren.mul, multiplier8x8, div8bit, rol8bit,
sub16/32bit) where infer_inputs_for_gate's pattern matchers don't
yet cover the specific substructure layout. Those need per-family
matchers added in a subsequent pass.
- build.py +18 -12
- build_all.py +13 -0
|
@@ -2978,23 +2978,29 @@ def build_inputs(tensors: Dict[str, torch.Tensor]) -> tuple[Dict[str, torch.Tens
|
|
| 2978 |
reg = SignalRegistry()
|
| 2979 |
gates = get_all_gates(tensors)
|
| 2980 |
stats = {"added": 0, "skipped": 0, "empty": 0, "regenerated": 0}
|
| 2981 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2982 |
inputs_key = f"{gate}.inputs"
|
| 2983 |
weight_key = f"{gate}.weight"
|
| 2984 |
if inputs_key in tensors:
|
| 2985 |
-
# Detect stale .inputs (length doesn't match the gate's fan-in)
|
| 2986 |
-
# for single-gate tensors and regenerate them. Packed multi-gate
|
| 2987 |
-
# tensors have weight.dim() > 1 and use a different convention,
|
| 2988 |
-
# so we leave their .inputs alone.
|
| 2989 |
-
existing = tensors[inputs_key]
|
| 2990 |
weight = tensors.get(weight_key)
|
| 2991 |
-
if
|
| 2992 |
-
|
| 2993 |
-
del tensors[inputs_key]
|
| 2994 |
-
stats["regenerated"] += 1
|
| 2995 |
-
else:
|
| 2996 |
-
stats["skipped"] += 1
|
| 2997 |
continue
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2998 |
inputs = infer_inputs_for_gate(gate, reg, tensors)
|
| 2999 |
if inputs:
|
| 3000 |
tensors[inputs_key] = torch.tensor(inputs, dtype=torch.int64)
|
|
|
|
| 2978 |
reg = SignalRegistry()
|
| 2979 |
gates = get_all_gates(tensors)
|
| 2980 |
stats = {"added": 0, "skipped": 0, "empty": 0, "regenerated": 0}
|
| 2981 |
+
|
| 2982 |
+
# Signal IDs are assigned freshly every run from a new SignalRegistry, so
|
| 2983 |
+
# any pre-existing .inputs tensors reference stale IDs from a prior build.
|
| 2984 |
+
# Drop them up front (except on packed multi-gate tensors, which use a
|
| 2985 |
+
# different convention) and regenerate everything below.
|
| 2986 |
+
preexisting_count = 0
|
| 2987 |
+
for gate in list(gates):
|
| 2988 |
inputs_key = f"{gate}.inputs"
|
| 2989 |
weight_key = f"{gate}.weight"
|
| 2990 |
if inputs_key in tensors:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2991 |
weight = tensors.get(weight_key)
|
| 2992 |
+
if weight is not None and weight.dim() > 1:
|
| 2993 |
+
# Packed multi-gate tensor; don't touch.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2994 |
continue
|
| 2995 |
+
del tensors[inputs_key]
|
| 2996 |
+
preexisting_count += 1
|
| 2997 |
+
stats["regenerated"] = preexisting_count
|
| 2998 |
+
|
| 2999 |
+
for gate in sorted(gates):
|
| 3000 |
+
inputs_key = f"{gate}.inputs"
|
| 3001 |
+
if inputs_key in tensors:
|
| 3002 |
+
stats["skipped"] += 1
|
| 3003 |
+
continue
|
| 3004 |
inputs = infer_inputs_for_gate(gate, reg, tensors)
|
| 3005 |
if inputs:
|
| 3006 |
tensors[inputs_key] = torch.tensor(inputs, dtype=torch.int64)
|
|
@@ -63,6 +63,16 @@ def build_variant(bits: int, profile: str) -> Path:
|
|
| 63 |
return out
|
| 64 |
|
| 65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
def quantize_variant(path: Path) -> tuple[int, int]:
|
| 67 |
"""Run quantize.py on a built variant. Returns (bytes_before, bytes_after)."""
|
| 68 |
rc, log = run([sys.executable, str(ROOT / "quantize.py"), str(path)], timeout=300)
|
|
@@ -151,6 +161,9 @@ def main() -> None:
|
|
| 151 |
pre_q_meta = measure_variant(path)
|
| 152 |
# Quantize in-place; weights are integer-valued so this is exact.
|
| 153 |
qb, qa = quantize_variant(path)
|
|
|
|
|
|
|
|
|
|
| 154 |
meta = measure_variant(path)
|
| 155 |
ev = eval_variant(path, device="cpu", timeout=900)
|
| 156 |
rows.append({
|
|
|
|
| 63 |
return out
|
| 64 |
|
| 65 |
|
| 66 |
+
def regenerate_inputs(path: Path) -> None:
|
| 67 |
+
"""Re-run `build.py inputs` so the routing metadata reflects gates added
|
| 68 |
+
or replaced by post-build steps (notably quantize.py --ternary, which
|
| 69 |
+
rebuilds modular detectors with a new gate name structure)."""
|
| 70 |
+
cmd = [sys.executable, str(ROOT / "build.py"), "--apply", "--model", str(path), "inputs"]
|
| 71 |
+
rc, log = run(cmd, timeout=300)
|
| 72 |
+
if rc != 0:
|
| 73 |
+
raise RuntimeError(f"inputs regeneration failed for {path.name}:\n{log[-800:]}")
|
| 74 |
+
|
| 75 |
+
|
| 76 |
def quantize_variant(path: Path) -> tuple[int, int]:
|
| 77 |
"""Run quantize.py on a built variant. Returns (bytes_before, bytes_after)."""
|
| 78 |
rc, log = run([sys.executable, str(ROOT / "quantize.py"), str(path)], timeout=300)
|
|
|
|
| 161 |
pre_q_meta = measure_variant(path)
|
| 162 |
# Quantize in-place; weights are integer-valued so this is exact.
|
| 163 |
qb, qa = quantize_variant(path)
|
| 164 |
+
# Quantize replaces some gates (modular detectors); rebuild
|
| 165 |
+
# routing metadata so .inputs covers the new structure.
|
| 166 |
+
regenerate_inputs(path)
|
| 167 |
meta = measure_variant(path)
|
| 168 |
ev = eval_variant(path, device="cpu", timeout=900)
|
| 169 |
rows.append({
|