CharlesCNorton commited on
Commit ·
e669ef2
1
Parent(s): 5ad204f
Make inputs coverage strict and document full verbose runs
Browse files
README.md
CHANGED
|
@@ -115,6 +115,7 @@ python eval.py
|
|
| 115 |
```
|
| 116 |
|
| 117 |
Tests all circuits exhaustively. 8-bit operations test all 256 or 65,536 input combinations. Float16 tests cover special cases (NaN, Inf, ±0, subnormals) plus normal arithmetic.
|
|
|
|
| 118 |
|
| 119 |
For coverage and input-routing validation:
|
| 120 |
|
|
@@ -122,7 +123,7 @@ For coverage and input-routing validation:
|
|
| 122 |
python eval.py --coverage --inputs-coverage
|
| 123 |
```
|
| 124 |
|
| 125 |
-
`--inputs-coverage` evaluates gates via their `.inputs` tensors
|
| 126 |
|
| 127 |
## Development History
|
| 128 |
|
|
|
|
| 115 |
```
|
| 116 |
|
| 117 |
Tests all circuits exhaustively. 8-bit operations test all 256 or 65,536 input combinations. Float16 tests cover special cases (NaN, Inf, ±0, subnormals) plus normal arithmetic.
|
| 118 |
+
Eval runs full + verbose by default; there is no quick/verbose mode. Use --circuit to filter reported circuits.
|
| 119 |
|
| 120 |
For coverage and input-routing validation:
|
| 121 |
|
|
|
|
| 123 |
python eval.py --coverage --inputs-coverage
|
| 124 |
```
|
| 125 |
|
| 126 |
+
`--inputs-coverage` evaluates gates via their `.inputs` tensors using seeded external inputs and explicit overrides, and fails if inputs cannot be resolved. This is for coverage and routing sanity, not a correctness proof.
|
| 127 |
|
| 128 |
## Development History
|
| 129 |
|
eval.py
CHANGED
|
@@ -3,11 +3,8 @@
|
|
| 3 |
Unified evaluator for threshold-calculus circuits.
|
| 4 |
|
| 5 |
Usage:
|
| 6 |
-
python eval.py # Run all tests
|
| 7 |
-
python eval.py --category float16 # Run only float16 tests
|
| 8 |
python eval.py --circuit float16.add # Run specific circuit
|
| 9 |
-
python eval.py --quick # Quick mode (fewer test cases)
|
| 10 |
-
python eval.py --verbose # Show all test details
|
| 11 |
python eval.py --json # Output JSON for CI
|
| 12 |
python eval.py --coverage # Show detailed coverage report
|
| 13 |
python eval.py --inputs-coverage # Sweep all gates using .inputs tensors
|
|
@@ -160,7 +157,8 @@ def evaluate_circuit(ctx: EvalContext, prefix: str, input_bits: torch.Tensor,
|
|
| 160 |
return torch.stack(outputs, dim=-1) if outputs else torch.tensor([])
|
| 161 |
|
| 162 |
|
| 163 |
-
def seed_external_signals(ctx: EvalContext, rng: random.Random
|
|
|
|
| 164 |
"""Seed external input signals and constants with random 0/1 values."""
|
| 165 |
signals: Dict[int, float] = {}
|
| 166 |
|
|
@@ -176,20 +174,68 @@ def seed_external_signals(ctx: EvalContext, rng: random.Random) -> Dict[int, flo
|
|
| 176 |
if sid not in signals:
|
| 177 |
signals[sid] = float(rng.getrandbits(1))
|
| 178 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
return signals
|
| 180 |
|
| 181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
def evaluate_gates_from_inputs(ctx: EvalContext, signals: Dict[int, float],
|
| 183 |
-
gate_list: Optional[List[str]] = None,
|
| 184 |
-
|
| 185 |
-
fill_missing: bool = False) -> Tuple[int, List[str], List[str], int]:
|
| 186 |
-
"""Evaluate gates using explicit .inputs tensors. Returns (evaluated, missing_inputs, unresolved, filled_ids)."""
|
| 187 |
gates = gate_list if gate_list is not None else ctx.gates
|
| 188 |
remaining = set(gates)
|
| 189 |
missing_inputs: List[str] = []
|
| 190 |
unresolved: List[str] = []
|
| 191 |
evaluated = 0
|
| 192 |
-
|
| 193 |
|
| 194 |
progress = True
|
| 195 |
while progress and remaining:
|
|
@@ -205,7 +251,17 @@ def evaluate_gates_from_inputs(ctx: EvalContext, signals: Dict[int, float],
|
|
| 205 |
continue
|
| 206 |
|
| 207 |
input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()]
|
| 208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
continue
|
| 210 |
|
| 211 |
weight = ctx.tensors[weight_key].tolist()
|
|
@@ -216,6 +272,8 @@ def evaluate_gates_from_inputs(ctx: EvalContext, signals: Dict[int, float],
|
|
| 216 |
gate_id = ctx.name_to_id.get(gate)
|
| 217 |
if gate_id is not None:
|
| 218 |
signals[gate_id] = out
|
|
|
|
|
|
|
| 219 |
|
| 220 |
if inputs_key in ctx.tensors:
|
| 221 |
ctx.tested_tensors.add(inputs_key)
|
|
@@ -228,26 +286,10 @@ def evaluate_gates_from_inputs(ctx: EvalContext, signals: Dict[int, float],
|
|
| 228 |
remaining.remove(gate)
|
| 229 |
progress = True
|
| 230 |
|
| 231 |
-
if not progress and remaining and fill_missing and rng is not None:
|
| 232 |
-
missing_ids = set()
|
| 233 |
-
for gate in remaining:
|
| 234 |
-
inputs_key = f"{gate}.inputs"
|
| 235 |
-
if inputs_key not in ctx.tensors:
|
| 236 |
-
continue
|
| 237 |
-
input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()]
|
| 238 |
-
for sid in input_ids:
|
| 239 |
-
if sid not in signals:
|
| 240 |
-
missing_ids.add(sid)
|
| 241 |
-
if missing_ids:
|
| 242 |
-
for sid in missing_ids:
|
| 243 |
-
signals[sid] = float(rng.getrandbits(1))
|
| 244 |
-
filled_ids += len(missing_ids)
|
| 245 |
-
progress = True
|
| 246 |
-
|
| 247 |
if remaining:
|
| 248 |
unresolved = sorted(remaining)
|
| 249 |
|
| 250 |
-
return evaluated, missing_inputs, unresolved
|
| 251 |
|
| 252 |
|
| 253 |
# =============================================================================
|
|
@@ -341,11 +383,12 @@ def inputs_coverage_sweep(ctx: EvalContext, seed: int = 0, verbose: bool = False
|
|
| 341 |
quiet: bool = False) -> None:
|
| 342 |
"""Evaluate all gates via .inputs to improve coverage."""
|
| 343 |
rng = random.Random(seed)
|
| 344 |
-
|
|
|
|
|
|
|
|
|
|
| 345 |
|
| 346 |
-
evaluated, missing_inputs, unresolved
|
| 347 |
-
ctx, signals, rng=rng, fill_missing=True
|
| 348 |
-
)
|
| 349 |
total = len(ctx.gates)
|
| 350 |
|
| 351 |
orphan_tensors = 0
|
|
@@ -357,12 +400,17 @@ def inputs_coverage_sweep(ctx: EvalContext, seed: int = 0, verbose: bool = False
|
|
| 357 |
ctx.tested_tensors.add(name)
|
| 358 |
orphan_tensors += 1
|
| 359 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 360 |
if quiet:
|
| 361 |
return
|
| 362 |
|
| 363 |
print(f"\nInput-coverage sweep: evaluated {evaluated}/{total} gates")
|
| 364 |
-
if filled_ids:
|
| 365 |
-
print(f" Filled missing signal IDs: {filled_ids}")
|
| 366 |
if orphan_tensors:
|
| 367 |
print(f" Orphan tensors touched: {orphan_tensors}")
|
| 368 |
if missing_inputs:
|
|
@@ -444,6 +492,19 @@ def bits_to_int_msb(bits: List[float]) -> int:
|
|
| 444 |
return val
|
| 445 |
|
| 446 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 447 |
def int_to_bits(val: int, n: int, signed: bool = False) -> List[float]:
|
| 448 |
"""Convert integer to n bits."""
|
| 449 |
if signed and val < 0:
|
|
@@ -2151,10 +2212,7 @@ def print_summary(results: List[TestResult], ctx: EvalContext,
|
|
| 2151 |
def main():
|
| 2152 |
parser = argparse.ArgumentParser(description="Unified evaluator for threshold-calculus circuits")
|
| 2153 |
parser.add_argument("--model", default="./arithmetic.safetensors", help="Path to model file")
|
| 2154 |
-
parser.add_argument("--category", "-c", action="append", help="Test specific category (can repeat)")
|
| 2155 |
parser.add_argument("--circuit", action="append", help="Test specific circuit (can repeat)")
|
| 2156 |
-
parser.add_argument("--quick", "-q", action="store_true", help="Quick mode (fewer test cases)")
|
| 2157 |
-
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
|
| 2158 |
parser.add_argument("--json", "-j", action="store_true", help="Output JSON for CI")
|
| 2159 |
parser.add_argument("--coverage", action="store_true", help="Show detailed coverage")
|
| 2160 |
parser.add_argument("--inputs-coverage", action="store_true", help="Sweep all gates using .inputs tensors")
|
|
@@ -2179,15 +2237,15 @@ def main():
|
|
| 2179 |
signals=signals,
|
| 2180 |
name_to_id=name_to_id,
|
| 2181 |
id_to_name=id_to_name,
|
| 2182 |
-
verbose=
|
| 2183 |
-
quick=
|
| 2184 |
)
|
| 2185 |
|
| 2186 |
start = time.time()
|
| 2187 |
-
results = run_tests(ctx, categories=
|
| 2188 |
|
| 2189 |
if args.coverage or args.inputs_coverage:
|
| 2190 |
-
inputs_coverage_sweep(ctx, seed=0, verbose=
|
| 2191 |
elapsed = time.time() - start
|
| 2192 |
|
| 2193 |
if args.json:
|
|
@@ -2200,7 +2258,7 @@ def main():
|
|
| 2200 |
}
|
| 2201 |
print(json.dumps(output, indent=2))
|
| 2202 |
else:
|
| 2203 |
-
print_summary(results, ctx, elapsed, verbose=
|
| 2204 |
|
| 2205 |
# Return exit code based on failures
|
| 2206 |
failed = [r for r in results if not r.success]
|
|
|
|
| 3 |
Unified evaluator for threshold-calculus circuits.
|
| 4 |
|
| 5 |
Usage:
|
| 6 |
+
python eval.py # Run all tests (always full + verbose)
|
|
|
|
| 7 |
python eval.py --circuit float16.add # Run specific circuit
|
|
|
|
|
|
|
| 8 |
python eval.py --json # Output JSON for CI
|
| 9 |
python eval.py --coverage # Show detailed coverage report
|
| 10 |
python eval.py --inputs-coverage # Sweep all gates using .inputs tensors
|
|
|
|
| 157 |
return torch.stack(outputs, dim=-1) if outputs else torch.tensor([])
|
| 158 |
|
| 159 |
|
| 160 |
+
def seed_external_signals(ctx: EvalContext, rng: random.Random,
|
| 161 |
+
extra_names: Optional[List[str]] = None) -> Dict[int, float]:
|
| 162 |
"""Seed external input signals and constants with random 0/1 values."""
|
| 163 |
signals: Dict[int, float] = {}
|
| 164 |
|
|
|
|
| 174 |
if sid not in signals:
|
| 175 |
signals[sid] = float(rng.getrandbits(1))
|
| 176 |
|
| 177 |
+
if extra_names:
|
| 178 |
+
for name in extra_names:
|
| 179 |
+
sid = ctx.name_to_id.get(name)
|
| 180 |
+
if sid is not None and sid not in signals:
|
| 181 |
+
signals[sid] = float(rng.getrandbits(1))
|
| 182 |
+
|
| 183 |
return signals
|
| 184 |
|
| 185 |
|
| 186 |
+
def resolve_alias_target(name: str, gates: set) -> Optional[str]:
|
| 187 |
+
"""Resolve common alias signal names to actual gate names."""
|
| 188 |
+
if name in gates:
|
| 189 |
+
return name
|
| 190 |
+
cand = name + ".layer2"
|
| 191 |
+
if cand in gates:
|
| 192 |
+
return cand
|
| 193 |
+
if name.endswith(".sum"):
|
| 194 |
+
cand = name[:-4] + ".xor2.layer2"
|
| 195 |
+
if cand in gates:
|
| 196 |
+
return cand
|
| 197 |
+
if name.endswith(".cout"):
|
| 198 |
+
for suffix in [".or_carry", ".carry_or"]:
|
| 199 |
+
cand = name[:-5] + suffix
|
| 200 |
+
if cand in gates:
|
| 201 |
+
return cand
|
| 202 |
+
return None
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
def build_alias_maps(ctx: EvalContext) -> Tuple[Dict[int, int], Dict[int, List[int]]]:
|
| 206 |
+
"""Build alias maps from orphan signals to actual gate outputs."""
|
| 207 |
+
gates = set(ctx.gates)
|
| 208 |
+
alias_to_gate: Dict[int, int] = {}
|
| 209 |
+
gate_to_alias: Dict[int, List[int]] = {}
|
| 210 |
+
|
| 211 |
+
for name, sid in ctx.name_to_id.items():
|
| 212 |
+
if name in ("#0", "#1"):
|
| 213 |
+
continue
|
| 214 |
+
if name.startswith("$") or ".$" in name:
|
| 215 |
+
continue
|
| 216 |
+
if name in gates:
|
| 217 |
+
continue
|
| 218 |
+
target = resolve_alias_target(name, gates)
|
| 219 |
+
if not target:
|
| 220 |
+
continue
|
| 221 |
+
target_id = ctx.name_to_id.get(target)
|
| 222 |
+
if target_id is None:
|
| 223 |
+
continue
|
| 224 |
+
alias_to_gate[sid] = target_id
|
| 225 |
+
gate_to_alias.setdefault(target_id, []).append(sid)
|
| 226 |
+
|
| 227 |
+
return alias_to_gate, gate_to_alias
|
| 228 |
+
|
| 229 |
+
|
| 230 |
def evaluate_gates_from_inputs(ctx: EvalContext, signals: Dict[int, float],
|
| 231 |
+
gate_list: Optional[List[str]] = None) -> Tuple[int, List[str], List[str]]:
|
| 232 |
+
"""Evaluate gates using explicit .inputs tensors. Returns (evaluated, missing_inputs, unresolved)."""
|
|
|
|
|
|
|
| 233 |
gates = gate_list if gate_list is not None else ctx.gates
|
| 234 |
remaining = set(gates)
|
| 235 |
missing_inputs: List[str] = []
|
| 236 |
unresolved: List[str] = []
|
| 237 |
evaluated = 0
|
| 238 |
+
alias_to_gate, gate_to_alias = build_alias_maps(ctx)
|
| 239 |
|
| 240 |
progress = True
|
| 241 |
while progress and remaining:
|
|
|
|
| 251 |
continue
|
| 252 |
|
| 253 |
input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()]
|
| 254 |
+
ready = True
|
| 255 |
+
for sid in input_ids:
|
| 256 |
+
if sid in signals:
|
| 257 |
+
continue
|
| 258 |
+
alias_gate = alias_to_gate.get(sid)
|
| 259 |
+
if alias_gate is not None and alias_gate in signals:
|
| 260 |
+
signals[sid] = signals[alias_gate]
|
| 261 |
+
continue
|
| 262 |
+
ready = False
|
| 263 |
+
break
|
| 264 |
+
if not ready:
|
| 265 |
continue
|
| 266 |
|
| 267 |
weight = ctx.tensors[weight_key].tolist()
|
|
|
|
| 272 |
gate_id = ctx.name_to_id.get(gate)
|
| 273 |
if gate_id is not None:
|
| 274 |
signals[gate_id] = out
|
| 275 |
+
for alias_id in gate_to_alias.get(gate_id, []):
|
| 276 |
+
signals[alias_id] = out
|
| 277 |
|
| 278 |
if inputs_key in ctx.tensors:
|
| 279 |
ctx.tested_tensors.add(inputs_key)
|
|
|
|
| 286 |
remaining.remove(gate)
|
| 287 |
progress = True
|
| 288 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 289 |
if remaining:
|
| 290 |
unresolved = sorted(remaining)
|
| 291 |
|
| 292 |
+
return evaluated, missing_inputs, unresolved
|
| 293 |
|
| 294 |
|
| 295 |
# =============================================================================
|
|
|
|
| 383 |
quiet: bool = False) -> None:
|
| 384 |
"""Evaluate all gates via .inputs to improve coverage."""
|
| 385 |
rng = random.Random(seed)
|
| 386 |
+
extra_names = []
|
| 387 |
+
for names in EXTERNAL_INPUT_OVERRIDES.values():
|
| 388 |
+
extra_names.extend(names)
|
| 389 |
+
signals = seed_external_signals(ctx, rng, extra_names=extra_names)
|
| 390 |
|
| 391 |
+
evaluated, missing_inputs, unresolved = evaluate_gates_from_inputs(ctx, signals)
|
|
|
|
|
|
|
| 392 |
total = len(ctx.gates)
|
| 393 |
|
| 394 |
orphan_tensors = 0
|
|
|
|
| 400 |
ctx.tested_tensors.add(name)
|
| 401 |
orphan_tensors += 1
|
| 402 |
|
| 403 |
+
# Hard failure on unresolved inputs
|
| 404 |
+
if missing_inputs or unresolved:
|
| 405 |
+
raise RuntimeError(
|
| 406 |
+
f"Unresolved inputs in input-coverage sweep: "
|
| 407 |
+
f"missing_inputs={len(missing_inputs)} unresolved={len(unresolved)}"
|
| 408 |
+
)
|
| 409 |
+
|
| 410 |
if quiet:
|
| 411 |
return
|
| 412 |
|
| 413 |
print(f"\nInput-coverage sweep: evaluated {evaluated}/{total} gates")
|
|
|
|
|
|
|
| 414 |
if orphan_tensors:
|
| 415 |
print(f" Orphan tensors touched: {orphan_tensors}")
|
| 416 |
if missing_inputs:
|
|
|
|
| 492 |
return val
|
| 493 |
|
| 494 |
|
| 495 |
+
# Explicit external signals needed to resolve orphan wiring (per circuit)
|
| 496 |
+
EXTERNAL_INPUT_OVERRIDES = {
|
| 497 |
+
"arithmetic.multiplier8x8": [
|
| 498 |
+
"arithmetic.multiplier8x8.stage0.bit9.ha2.sum.layer2",
|
| 499 |
+
"arithmetic.multiplier8x8.stage1.bit10.ha2.sum.layer2",
|
| 500 |
+
"arithmetic.multiplier8x8.stage2.bit11.ha2.sum.layer2",
|
| 501 |
+
"arithmetic.multiplier8x8.stage3.bit12.ha2.sum.layer2",
|
| 502 |
+
"arithmetic.multiplier8x8.stage4.bit13.ha2.sum.layer2",
|
| 503 |
+
"arithmetic.multiplier8x8.stage5.bit14.ha2.sum.layer2",
|
| 504 |
+
],
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
|
| 508 |
def int_to_bits(val: int, n: int, signed: bool = False) -> List[float]:
|
| 509 |
"""Convert integer to n bits."""
|
| 510 |
if signed and val < 0:
|
|
|
|
| 2212 |
def main():
|
| 2213 |
parser = argparse.ArgumentParser(description="Unified evaluator for threshold-calculus circuits")
|
| 2214 |
parser.add_argument("--model", default="./arithmetic.safetensors", help="Path to model file")
|
|
|
|
| 2215 |
parser.add_argument("--circuit", action="append", help="Test specific circuit (can repeat)")
|
|
|
|
|
|
|
| 2216 |
parser.add_argument("--json", "-j", action="store_true", help="Output JSON for CI")
|
| 2217 |
parser.add_argument("--coverage", action="store_true", help="Show detailed coverage")
|
| 2218 |
parser.add_argument("--inputs-coverage", action="store_true", help="Sweep all gates using .inputs tensors")
|
|
|
|
| 2237 |
signals=signals,
|
| 2238 |
name_to_id=name_to_id,
|
| 2239 |
id_to_name=id_to_name,
|
| 2240 |
+
verbose=True,
|
| 2241 |
+
quick=False,
|
| 2242 |
)
|
| 2243 |
|
| 2244 |
start = time.time()
|
| 2245 |
+
results = run_tests(ctx, categories=None, circuits=args.circuit)
|
| 2246 |
|
| 2247 |
if args.coverage or args.inputs_coverage:
|
| 2248 |
+
inputs_coverage_sweep(ctx, seed=0, verbose=True, quiet=args.json)
|
| 2249 |
elapsed = time.time() - start
|
| 2250 |
|
| 2251 |
if args.json:
|
|
|
|
| 2258 |
}
|
| 2259 |
print(json.dumps(output, indent=2))
|
| 2260 |
else:
|
| 2261 |
+
print_summary(results, ctx, elapsed, verbose=True)
|
| 2262 |
|
| 2263 |
# Return exit code based on failures
|
| 2264 |
failed = [r for r in results if not r.success]
|