CharlesCNorton commited on
Commit
719dc06
·
1 Parent(s): 2e671d4

Always regenerate .inputs from a fresh signal registry

Browse files

Previously 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.

Files changed (2) hide show
  1. build.py +18 -12
  2. build_all.py +13 -0
build.py CHANGED
@@ -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
- for gate in sorted(gates):
 
 
 
 
 
 
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 (weight is not None and weight.dim() == 1
2992
- and existing.numel() != weight.numel()):
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)
build_all.py CHANGED
@@ -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({