CharlesCNorton commited on
Commit
d4442da
·
1 Parent(s): f9ba712

Add inputs-coverage sweep and document coverage run

Browse files
Files changed (2) hide show
  1. README.md +8 -0
  2. eval.py +155 -5
README.md CHANGED
@@ -116,6 +116,14 @@ python eval.py
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
  ## Development History
120
 
121
  Started as an 8-bit CPU project. Built boolean gates, then arithmetic (adders → multipliers → dividers), then CPU control logic. The CPU worked but the arithmetic core turned out to be the useful part, so it was extracted.
 
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
+
121
+ ```bash
122
+ python eval.py --coverage --inputs-coverage
123
+ ```
124
+
125
+ `--inputs-coverage` evaluates gates via their `.inputs` tensors (using seeded/randomized external inputs to complete missing dependencies). This is for coverage and routing sanity, not a correctness proof.
126
+
127
  ## Development History
128
 
129
  Started as an 8-bit CPU project. Built boolean gates, then arithmetic (adders → multipliers → dividers), then CPU control logic. The CPU worked but the arithmetic core turned out to be the useful part, so it was extracted.
eval.py CHANGED
@@ -10,11 +10,13 @@ Usage:
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 --list # List available categories/circuits
14
  """
15
 
16
  import argparse
17
  import json
 
18
  import struct
19
  import sys
20
  import time
@@ -51,15 +53,17 @@ class EvalContext:
51
  gates: List[str]
52
  signals: Dict[str, int]
53
  name_to_id: Dict[str, int] = field(default_factory=dict)
 
54
  verbose: bool = False
55
  quick: bool = False
56
  tested_tensors: set = field(default_factory=set)
57
 
58
 
59
- def load_model(path: str = "./arithmetic.safetensors") -> Tuple[Dict[str, torch.Tensor], List[str], Dict[str, int], Dict[str, int]]:
60
  """Load model and extract gates and signals."""
61
  tensors = {}
62
  name_to_id = {}
 
63
  with safe_open(path, framework='pt') as f:
64
  for name in f.keys():
65
  tensors[name] = f.get_tensor(name)
@@ -67,6 +71,7 @@ def load_model(path: str = "./arithmetic.safetensors") -> Tuple[Dict[str, torch.
67
  if metadata and 'signal_registry' in metadata:
68
  registry_raw = json.loads(metadata['signal_registry'])
69
  name_to_id = {v: int(k) for k, v in registry_raw.items()}
 
70
 
71
  # Extract gates (tensors with .weight)
72
  gates = sorted(set(k.rsplit('.', 1)[0] for k in tensors.keys() if k.endswith('.weight')))
@@ -78,7 +83,7 @@ def load_model(path: str = "./arithmetic.safetensors") -> Tuple[Dict[str, torch.
78
  signals[gate] = signal_id
79
  signal_id += 1
80
 
81
- return tensors, gates, signals, name_to_id
82
 
83
 
84
  def evaluate_gate(ctx: EvalContext, gate: str, inputs: torch.Tensor) -> torch.Tensor:
@@ -90,7 +95,8 @@ def evaluate_gate(ctx: EvalContext, gate: str, inputs: torch.Tensor) -> torch.Te
90
  raise ValueError(f"Gate not found: {gate}")
91
 
92
  ctx.tested_tensors.add(weight_key)
93
- ctx.tested_tensors.add(bias_key)
 
94
 
95
  weight = ctx.tensors[weight_key]
96
  bias = ctx.tensors.get(bias_key, torch.tensor([0.0]))
@@ -154,6 +160,96 @@ def evaluate_circuit(ctx: EvalContext, prefix: str, input_bits: torch.Tensor,
154
  return torch.stack(outputs, dim=-1) if outputs else torch.tensor([])
155
 
156
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  # =============================================================================
158
  # DIRECT EVALUATION (simpler approach used by original evals)
159
  # =============================================================================
@@ -164,7 +260,8 @@ def eval_gate_direct(ctx: EvalContext, gate: str, inputs: List[float]) -> float:
164
  bias_key = f"{gate}.bias"
165
 
166
  ctx.tested_tensors.add(weight_key)
167
- ctx.tested_tensors.add(bias_key)
 
168
 
169
  weight = ctx.tensors[weight_key].tolist()
170
  bias = ctx.tensors.get(bias_key, torch.tensor([0.0])).item()
@@ -236,6 +333,54 @@ def eval_ripple_carry_adder(ctx: EvalContext, prefix: str, a_bits: List[float],
236
  return result
237
 
238
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  # =============================================================================
240
  # FLOAT16 UTILITIES
241
  # =============================================================================
@@ -1924,6 +2069,7 @@ def main():
1924
  parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
1925
  parser.add_argument("--json", "-j", action="store_true", help="Output JSON for CI")
1926
  parser.add_argument("--coverage", action="store_true", help="Show detailed coverage")
 
1927
  parser.add_argument("--list", "-l", action="store_true", help="List categories and exit")
1928
 
1929
  args = parser.parse_args()
@@ -1935,7 +2081,7 @@ def main():
1935
  return 0
1936
 
1937
  print(f"Loading model from {args.model}...")
1938
- tensors, gates, signals, name_to_id = load_model(args.model)
1939
 
1940
  print(f"Loaded {len(tensors)} tensors, {len(gates)} gates, {len(signals)} signals")
1941
 
@@ -1944,12 +2090,16 @@ def main():
1944
  gates=gates,
1945
  signals=signals,
1946
  name_to_id=name_to_id,
 
1947
  verbose=args.verbose,
1948
  quick=args.quick,
1949
  )
1950
 
1951
  start = time.time()
1952
  results = run_tests(ctx, categories=args.category, circuits=args.circuit)
 
 
 
1953
  elapsed = time.time() - start
1954
 
1955
  if args.json:
 
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
14
  python eval.py --list # List available categories/circuits
15
  """
16
 
17
  import argparse
18
  import json
19
+ import random
20
  import struct
21
  import sys
22
  import time
 
53
  gates: List[str]
54
  signals: Dict[str, int]
55
  name_to_id: Dict[str, int] = field(default_factory=dict)
56
+ id_to_name: Dict[int, str] = field(default_factory=dict)
57
  verbose: bool = False
58
  quick: bool = False
59
  tested_tensors: set = field(default_factory=set)
60
 
61
 
62
+ def load_model(path: str = "./arithmetic.safetensors") -> Tuple[Dict[str, torch.Tensor], List[str], Dict[str, int], Dict[str, int], Dict[int, str]]:
63
  """Load model and extract gates and signals."""
64
  tensors = {}
65
  name_to_id = {}
66
+ id_to_name = {}
67
  with safe_open(path, framework='pt') as f:
68
  for name in f.keys():
69
  tensors[name] = f.get_tensor(name)
 
71
  if metadata and 'signal_registry' in metadata:
72
  registry_raw = json.loads(metadata['signal_registry'])
73
  name_to_id = {v: int(k) for k, v in registry_raw.items()}
74
+ id_to_name = {int(k): v for k, v in registry_raw.items()}
75
 
76
  # Extract gates (tensors with .weight)
77
  gates = sorted(set(k.rsplit('.', 1)[0] for k in tensors.keys() if k.endswith('.weight')))
 
83
  signals[gate] = signal_id
84
  signal_id += 1
85
 
86
+ return tensors, gates, signals, name_to_id, id_to_name
87
 
88
 
89
  def evaluate_gate(ctx: EvalContext, gate: str, inputs: torch.Tensor) -> torch.Tensor:
 
95
  raise ValueError(f"Gate not found: {gate}")
96
 
97
  ctx.tested_tensors.add(weight_key)
98
+ if bias_key in ctx.tensors:
99
+ ctx.tested_tensors.add(bias_key)
100
 
101
  weight = ctx.tensors[weight_key]
102
  bias = ctx.tensors.get(bias_key, torch.tensor([0.0]))
 
160
  return torch.stack(outputs, dim=-1) if outputs else torch.tensor([])
161
 
162
 
163
+ def seed_external_signals(ctx: EvalContext, rng: random.Random) -> Dict[int, float]:
164
+ """Seed external input signals and constants with random 0/1 values."""
165
+ signals: Dict[int, float] = {}
166
+
167
+ # Constants
168
+ if "#0" in ctx.name_to_id:
169
+ signals[ctx.name_to_id["#0"]] = 0.0
170
+ if "#1" in ctx.name_to_id:
171
+ signals[ctx.name_to_id["#1"]] = 1.0
172
+
173
+ # External inputs (names starting with '$' or containing '.$')
174
+ for name, sid in ctx.name_to_id.items():
175
+ if name.startswith("$") or ".$" in name:
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
+ rng: Optional[random.Random] = None,
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
+ filled_ids = 0
193
+
194
+ progress = True
195
+ while progress and remaining:
196
+ progress = False
197
+ for gate in list(remaining):
198
+ inputs_key = f"{gate}.inputs"
199
+ weight_key = f"{gate}.weight"
200
+ bias_key = f"{gate}.bias"
201
+
202
+ if inputs_key not in ctx.tensors:
203
+ missing_inputs.append(gate)
204
+ remaining.remove(gate)
205
+ continue
206
+
207
+ input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()]
208
+ if not all(sid in signals for sid in input_ids):
209
+ continue
210
+
211
+ weight = ctx.tensors[weight_key].tolist()
212
+ bias = ctx.tensors.get(bias_key, torch.tensor([0.0])).item()
213
+ total = bias + sum(w * signals[sid] for w, sid in zip(weight, input_ids))
214
+ out = 1.0 if total >= 0 else 0.0
215
+
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)
222
+ if weight_key in ctx.tensors:
223
+ ctx.tested_tensors.add(weight_key)
224
+ if bias_key in ctx.tensors:
225
+ ctx.tested_tensors.add(bias_key)
226
+
227
+ evaluated += 1
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, filled_ids
251
+
252
+
253
  # =============================================================================
254
  # DIRECT EVALUATION (simpler approach used by original evals)
255
  # =============================================================================
 
260
  bias_key = f"{gate}.bias"
261
 
262
  ctx.tested_tensors.add(weight_key)
263
+ if bias_key in ctx.tensors:
264
+ ctx.tested_tensors.add(bias_key)
265
 
266
  weight = ctx.tensors[weight_key].tolist()
267
  bias = ctx.tensors.get(bias_key, torch.tensor([0.0])).item()
 
333
  return result
334
 
335
 
336
+ # =============================================================================
337
+ # INPUT-ROUTED COVERAGE SWEEP
338
+ # =============================================================================
339
+
340
+ 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
+ signals = seed_external_signals(ctx, rng)
345
+
346
+ evaluated, missing_inputs, unresolved, filled_ids = evaluate_gates_from_inputs(
347
+ ctx, signals, rng=rng, fill_missing=True
348
+ )
349
+ total = len(ctx.gates)
350
+
351
+ orphan_tensors = 0
352
+ for name in ctx.tensors.keys():
353
+ if name in ctx.tested_tensors:
354
+ continue
355
+ if name.endswith(".weight") or name.endswith(".bias") or name.endswith(".inputs"):
356
+ continue
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:
369
+ print(f" Gates missing .inputs: {len(missing_inputs)}")
370
+ if verbose:
371
+ for g in sorted(missing_inputs)[:20]:
372
+ print(f" - {g}")
373
+ if len(missing_inputs) > 20:
374
+ print(f" ... and {len(missing_inputs) - 20} more")
375
+ if unresolved:
376
+ print(f" Gates unresolved (missing signal deps): {len(unresolved)}")
377
+ if verbose:
378
+ for g in unresolved[:20]:
379
+ print(f" - {g}")
380
+ if len(unresolved) > 20:
381
+ print(f" ... and {len(unresolved) - 20} more")
382
+
383
+
384
  # =============================================================================
385
  # FLOAT16 UTILITIES
386
  # =============================================================================
 
2069
  parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
2070
  parser.add_argument("--json", "-j", action="store_true", help="Output JSON for CI")
2071
  parser.add_argument("--coverage", action="store_true", help="Show detailed coverage")
2072
+ parser.add_argument("--inputs-coverage", action="store_true", help="Sweep all gates using .inputs tensors")
2073
  parser.add_argument("--list", "-l", action="store_true", help="List categories and exit")
2074
 
2075
  args = parser.parse_args()
 
2081
  return 0
2082
 
2083
  print(f"Loading model from {args.model}...")
2084
+ tensors, gates, signals, name_to_id, id_to_name = load_model(args.model)
2085
 
2086
  print(f"Loaded {len(tensors)} tensors, {len(gates)} gates, {len(signals)} signals")
2087
 
 
2090
  gates=gates,
2091
  signals=signals,
2092
  name_to_id=name_to_id,
2093
+ id_to_name=id_to_name,
2094
  verbose=args.verbose,
2095
  quick=args.quick,
2096
  )
2097
 
2098
  start = time.time()
2099
  results = run_tests(ctx, categories=args.category, circuits=args.circuit)
2100
+
2101
+ if args.coverage or args.inputs_coverage:
2102
+ inputs_coverage_sweep(ctx, seed=0, verbose=args.verbose, quiet=args.json)
2103
  elapsed = time.time() - start
2104
 
2105
  if args.json: