CharlesCNorton commited on
Commit
10e432e
·
1 Parent(s): e669ef2

Add randomized float16 tests and document open issues

Browse files
Files changed (2) hide show
  1. README.md +9 -0
  2. eval.py +271 -163
README.md CHANGED
@@ -153,6 +153,15 @@ This began as an attempt to build a complete threshold-logic CPU. The CPU is in
153
  - Rip out 8-bit integer circuits, replace with 16-bit
154
  - 8-bit was scaffolding for float16 development, not the product
155
 
 
 
 
 
 
 
 
 
 
156
  ## License
157
 
158
  Apache 2.0
 
153
  - Rip out 8-bit integer circuits, replace with 16-bit
154
  - 8-bit was scaffolding for float16 development, not the product
155
 
156
+ ## TODO (Unified)
157
+
158
+ 1. Fix float16 add/sub correctness for subnormals/zero, opposite-sign cancellation, and overflow/infinity sign handling.
159
+ 2. Fix float16 mul/div correctness for subnormals/zero, sign propagation, and special-case handling.
160
+ 3. Fix float16 toint/fromint correctness: stop zeroing valid values, define rounding (truncate vs round-to-nearest-even), and align tests with the chosen spec.
161
+ 4. Define accuracy/rounding specs and implement float16 sqrt/rsqrt/pow/exp/ln/log2.
162
+ 5. Implement float16 trig (sin/cos/tan via CORDIC) and tanh with explicit accuracy targets.
163
+ 6. Replace 8-bit integer circuits with 16-bit and remove 8-bit scaffolding.
164
+
165
  ## License
166
 
167
  Apache 2.0
eval.py CHANGED
@@ -512,6 +512,128 @@ def int_to_bits(val: int, n: int, signed: bool = False) -> List[float]:
512
  return [float((val >> i) & 1) for i in range(n)]
513
 
514
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
  # =============================================================================
516
  # BOOLEAN GATE TESTS
517
  # =============================================================================
@@ -1901,177 +2023,125 @@ def test_float16_arithmetic(ctx: EvalContext) -> List[TestResult]:
1901
  """Test float16 arithmetic operations."""
1902
  results = []
1903
 
1904
- # Addition - mark tensors as tested
 
 
 
 
1905
  if f"float16.add.exp_a_all_ones.weight" in ctx.tensors:
1906
  passed, total = 0, 0
1907
- test_pairs = [
1908
- (0.0, 0.0), (1.0, 0.0), (0.0, 1.0), (1.0, 1.0),
1909
- (1.0, -1.0), (-1.0, 1.0), (0.5, 0.5), (0.5, 0.25),
1910
- (100.0, 200.0), (-100.0, -200.0), (65504.0, 0.0),
1911
- ]
1912
- if not ctx.quick:
1913
- test_pairs.extend([(1.0, 2.0), (3.0, 4.0), (10.0, 20.0)])
1914
 
1915
- for a, b in test_pairs:
1916
- a_bits = float_to_bits(a)
1917
- b_bits = float_to_bits(b)
1918
-
1919
- # Mark key tensors as tested
1920
- for tensor_name in ["exp_a_all_ones", "exp_b_all_ones", "exp_a_zero", "exp_b_zero",
1921
- "mant_a_nonzero", "mant_b_nonzero", "a_is_nan", "b_is_nan"]:
1922
- ctx.tested_tensors.add(f"float16.add.{tensor_name}.weight")
1923
- ctx.tested_tensors.add(f"float16.add.{tensor_name}.bias")
1924
 
1925
  total += 1
1926
- passed += 1 # Simplified
 
 
 
 
 
 
 
 
1927
 
1928
- results.append(TestResult("float16.add", passed, total))
1929
 
1930
- # Subtraction
1931
  if f"float16.sub.b_neg_sign.weight" in ctx.tensors:
1932
  passed, total = 0, 0
1933
- test_pairs = [(1.0, 0.0), (0.0, 1.0), (1.0, 1.0), (2.0, 1.0), (1.0, 2.0)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1934
 
1935
- for a, b in test_pairs:
1936
- ctx.tested_tensors.add("float16.sub.b_neg_sign.weight")
1937
  total += 1
1938
- passed += 1
 
 
 
 
 
 
 
 
1939
 
1940
- results.append(TestResult("float16.sub", passed, total))
1941
 
1942
- # Multiplication - actual circuit evaluation
1943
  if f"float16.mul.exp_a_all_ones.weight" in ctx.tensors:
1944
  passed, total = 0, 0
1945
- test_pairs = [(0.0, 0.0), (1.0, 1.0), (2.0, 3.0), (0.5, 2.0), (-1.0, 1.0),
1946
- (2.0, 2.0), (1.5, 2.0), (-1.0, -1.0), (3.14, 2.0), (100.0, 0.01)]
1947
-
1948
- mul_gates = sorted([k.rsplit('.weight', 1)[0] for k in ctx.tensors.keys()
1949
- if k.startswith('float16.mul.') and k.endswith('.weight')])
1950
 
1951
- for a, b in test_pairs:
1952
- a_bits = float_to_int(a)
1953
- b_bits = float_to_int(b)
1954
- expected_bits = float_to_int(a * b)
1955
-
1956
- # Evaluate circuit
1957
- signals = {0: 0, 1: 1}
1958
- for i in range(16):
1959
- aid = ctx.name_to_id.get(f'float16.mul.$a[{i}]')
1960
- bid = ctx.name_to_id.get(f'float16.mul.$b[{i}]')
1961
- if aid: signals[aid] = (a_bits >> i) & 1
1962
- if bid: signals[bid] = (b_bits >> i) & 1
1963
-
1964
- evaluated = set()
1965
- for _ in range(len(mul_gates) * 3):
1966
- progress = False
1967
- for gate in mul_gates:
1968
- if gate in evaluated:
1969
- continue
1970
- inputs_key = f'{gate}.inputs'
1971
- if inputs_key not in ctx.tensors:
1972
- continue
1973
- input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()]
1974
- if not all(sid in signals for sid in input_ids):
1975
- continue
1976
- weight = ctx.tensors[f'{gate}.weight'].tolist()
1977
- bias = ctx.tensors.get(f'{gate}.bias', torch.tensor([0.0])).item()
1978
- total_sum = bias + sum(w * signals[sid] for w, sid in zip(weight, input_ids))
1979
- result = 1 if total_sum >= 0 else 0
1980
- gate_id = ctx.name_to_id.get(gate)
1981
- if gate_id is not None:
1982
- signals[gate_id] = result
1983
- evaluated.add(gate)
1984
- ctx.tested_tensors.add(f'{gate}.weight')
1985
- ctx.tested_tensors.add(f'{gate}.bias')
1986
- progress = True
1987
- if not progress:
1988
- break
1989
-
1990
- result_bits = 0
1991
- for i in range(16):
1992
- gate = f'float16.mul.out{i}'
1993
- inputs_key = f'{gate}.inputs'
1994
- if inputs_key in ctx.tensors:
1995
- input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()]
1996
- if all(sid in signals for sid in input_ids):
1997
- weight = ctx.tensors[f'{gate}.weight'].tolist()
1998
- bias = ctx.tensors.get(f'{gate}.bias', torch.tensor([0.0])).item()
1999
- total_sum = bias + sum(w * signals[sid] for w, sid in zip(weight, input_ids))
2000
- if total_sum >= 0:
2001
- result_bits |= (1 << i)
2002
 
2003
  total += 1
2004
- if result_bits == expected_bits:
2005
  passed += 1
 
 
 
 
 
 
 
2006
 
2007
- results.append(TestResult("float16.mul", passed, total))
2008
 
2009
- # Division - actual circuit evaluation
2010
  if f"float16.div.exp_a_all_ones.weight" in ctx.tensors:
2011
  passed, total = 0, 0
2012
- test_pairs = [(0.0, 1.0), (1.0, 1.0), (4.0, 2.0), (1.0, 2.0),
2013
- (8.0, -2.0), (2.0, 4.0), (-4.0, -2.0)]
2014
-
2015
- div_gates = sorted([k.rsplit('.weight', 1)[0] for k in ctx.tensors.keys()
2016
- if k.startswith('float16.div.') and k.endswith('.weight')])
2017
-
2018
- for a, b in test_pairs:
2019
- a_bits = float_to_int(a)
2020
- b_bits = float_to_int(b)
2021
- expected = a / b if b != 0 else float('inf')
2022
- expected_bits = float_to_int(expected)
2023
-
2024
- signals = {0: 0, 1: 1}
2025
- for i in range(16):
2026
- aid = ctx.name_to_id.get(f'float16.div.$a[{i}]')
2027
- bid = ctx.name_to_id.get(f'float16.div.$b[{i}]')
2028
- if aid: signals[aid] = (a_bits >> i) & 1
2029
- if bid: signals[bid] = (b_bits >> i) & 1
2030
-
2031
- evaluated = set()
2032
- for _ in range(len(div_gates) * 3):
2033
- progress = False
2034
- for gate in div_gates:
2035
- if gate in evaluated:
2036
- continue
2037
- inputs_key = f'{gate}.inputs'
2038
- if inputs_key not in ctx.tensors:
2039
- continue
2040
- input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()]
2041
- if not all(sid in signals for sid in input_ids):
2042
- continue
2043
- weight = ctx.tensors[f'{gate}.weight'].tolist()
2044
- bias = ctx.tensors.get(f'{gate}.bias', torch.tensor([0.0])).item()
2045
- total_sum = bias + sum(w * signals[sid] for w, sid in zip(weight, input_ids))
2046
- result = 1 if total_sum >= 0 else 0
2047
- gate_id = ctx.name_to_id.get(gate)
2048
- if gate_id is not None:
2049
- signals[gate_id] = result
2050
- evaluated.add(gate)
2051
- ctx.tested_tensors.add(f'{gate}.weight')
2052
- ctx.tested_tensors.add(f'{gate}.bias')
2053
- progress = True
2054
- if not progress:
2055
- break
2056
 
2057
- result_bits = 0
2058
- for i in range(16):
2059
- gate = f'float16.div.out{i}'
2060
- inputs_key = f'{gate}.inputs'
2061
- if inputs_key in ctx.tensors:
2062
- input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()]
2063
- if all(sid in signals for sid in input_ids):
2064
- weight = ctx.tensors[f'{gate}.weight'].tolist()
2065
- bias = ctx.tensors.get(f'{gate}.bias', torch.tensor([0.0])).item()
2066
- total_sum = bias + sum(w * signals[sid] for w, sid in zip(weight, input_ids))
2067
- if total_sum >= 0:
2068
- result_bits |= (1 << i)
2069
 
2070
  total += 1
2071
- if result_bits == expected_bits:
2072
  passed += 1
 
 
 
 
 
 
 
2073
 
2074
- results.append(TestResult("float16.div", passed, total))
2075
 
2076
  return results
2077
 
@@ -2080,42 +2150,80 @@ def test_float16_conversion(ctx: EvalContext) -> List[TestResult]:
2080
  """Test float16 conversion operations."""
2081
  results = []
2082
 
 
 
2083
  # toint
2084
  if f"float16.toint.exp_all_ones.weight" in ctx.tensors:
2085
  passed, total = 0, 0
2086
- test_values = [0.0, 1.0, -1.0, 2.0, -2.0, 0.5, -0.5, 100.0, -100.0, 32767.0]
 
2087
 
2088
- for val in test_values:
2089
- bits = float_to_bits(val)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2090
 
2091
- # Mark tensors as tested
2092
- for name in ["exp_all_ones", "exp_zero", "mant_nonzero", "is_nan", "is_inf"]:
2093
- ctx.tested_tensors.add(f"float16.toint.{name}.weight")
2094
- ctx.tested_tensors.add(f"float16.toint.{name}.bias")
2095
 
2096
- expected = int(val) if -32768 <= val <= 32767 else 0
2097
  total += 1
2098
- passed += 1 # Simplified
 
 
 
 
 
 
 
2099
 
2100
- results.append(TestResult("float16.toint", passed, total))
2101
 
2102
  # fromint
2103
  if f"float16.fromint.is_zero.weight" in ctx.tensors:
2104
  passed, total = 0, 0
2105
- test_values = [0, 1, -1, 2, -2, 100, -100, 32767, -32768]
 
2106
 
2107
- for val in test_values:
2108
- bits = int_to_bits(val, 16, signed=True)
 
 
2109
 
2110
- # Mark tensors as tested
2111
- for name in ["is_zero", "is_negative"]:
2112
- ctx.tested_tensors.add(f"float16.fromint.{name}.weight")
2113
- ctx.tested_tensors.add(f"float16.fromint.{name}.bias")
2114
 
 
2115
  total += 1
2116
- passed += 1 # Simplified
2117
-
2118
- results.append(TestResult("float16.fromint", passed, total))
 
 
 
 
 
 
 
2119
 
2120
  return results
2121
 
 
512
  return [float((val >> i) & 1) for i in range(n)]
513
 
514
 
515
+ def float16_int_to_float(val: int) -> float:
516
+ """Interpret a 16-bit int as IEEE-754 float16."""
517
+ packed = struct.pack('>H', val & 0xFFFF)
518
+ return struct.unpack('>e', packed)[0]
519
+
520
+
521
+ def float16_is_nan_bits(val: int) -> bool:
522
+ """Return True if the 16-bit pattern encodes a NaN."""
523
+ return (val & 0x7C00) == 0x7C00 and (val & 0x03FF) != 0
524
+
525
+
526
+ def seed_prefix_bits(ctx: EvalContext, prefix: str, base: str,
527
+ bits: List[float], signals: Dict[int, float]) -> None:
528
+ """Seed signals for prefix.$base[i] inputs using bits list."""
529
+ names = [n for n in ctx.name_to_id.keys() if n.startswith(f"{prefix}.${base}[")]
530
+ if not names:
531
+ raise RuntimeError(f"{prefix}: no inputs found for ${base}")
532
+ for name in names:
533
+ try:
534
+ idx = int(name.split("[", 1)[1].split("]", 1)[0])
535
+ except (IndexError, ValueError):
536
+ raise RuntimeError(f"{prefix}: bad input name {name}")
537
+ if idx >= len(bits):
538
+ raise RuntimeError(f"{prefix}: missing bit {idx} for ${base}")
539
+ signals[ctx.name_to_id[name]] = float(bits[idx])
540
+
541
+
542
+ def eval_prefix_outputs(ctx: EvalContext, prefix: str,
543
+ inputs: Dict[str, List[float]],
544
+ gate_list: Optional[List[str]] = None,
545
+ out_bits: int = 16) -> List[float]:
546
+ """Evaluate a circuit prefix using .inputs routing and return output bits."""
547
+ signals: Dict[int, float] = {}
548
+ if "#0" in ctx.name_to_id:
549
+ signals[ctx.name_to_id["#0"]] = 0.0
550
+ if "#1" in ctx.name_to_id:
551
+ signals[ctx.name_to_id["#1"]] = 1.0
552
+
553
+ for base, bits in inputs.items():
554
+ seed_prefix_bits(ctx, prefix, base, bits, signals)
555
+
556
+ gates = gate_list if gate_list is not None else [g for g in ctx.gates if g.startswith(prefix + ".")]
557
+ evaluated, missing_inputs, unresolved = evaluate_gates_from_inputs(ctx, signals, gate_list=gates)
558
+ if missing_inputs or unresolved:
559
+ raise RuntimeError(
560
+ f"{prefix}: unresolved inputs (missing={len(missing_inputs)} unresolved={len(unresolved)})"
561
+ )
562
+
563
+ outputs: List[float] = []
564
+ for i in range(out_bits):
565
+ gate = f"{prefix}.out{i}"
566
+ sid = ctx.name_to_id.get(gate)
567
+ if sid is not None and sid in signals:
568
+ outputs.append(float(signals[sid]))
569
+ continue
570
+ inputs_key = f"{gate}.inputs"
571
+ if inputs_key not in ctx.tensors:
572
+ raise RuntimeError(f"{prefix}: missing outputs for {gate}")
573
+ input_ids = [int(x) for x in ctx.tensors[inputs_key].tolist()]
574
+ input_vals = [signals[sid] for sid in input_ids]
575
+ outputs.append(eval_gate_direct(ctx, gate, input_vals))
576
+
577
+ return outputs
578
+
579
+
580
+ def build_float16_pairs(rng: random.Random, count: int) -> List[Tuple[int, int]]:
581
+ """Build deterministic float16 test pairs using edge cases + random."""
582
+ edges = [
583
+ 0x0000, # +0
584
+ 0x8000, # -0
585
+ 0x3C00, # 1.0
586
+ 0xBC00, # -1.0
587
+ 0x4000, # 2.0
588
+ 0xC000, # -2.0
589
+ 0x3E00, # 1.5
590
+ 0x3555, # ~0.333
591
+ 0x7BFF, # max finite
592
+ 0xFBFF, # min finite
593
+ 0x0400, # min normal
594
+ 0x0001, # min subnormal
595
+ 0x03FF, # max subnormal
596
+ 0x7C00, # +inf
597
+ 0xFC00, # -inf
598
+ 0x7E00, # NaN
599
+ ]
600
+ pairs = [(a, b) for a in edges for b in edges]
601
+ rng.shuffle(pairs)
602
+ pairs = pairs[:min(len(pairs), count)]
603
+
604
+ seen = set(pairs)
605
+ while len(pairs) < count:
606
+ a = rng.getrandbits(16)
607
+ b = rng.getrandbits(16)
608
+ if (a, b) in seen:
609
+ continue
610
+ seen.add((a, b))
611
+ pairs.append((a, b))
612
+
613
+ return pairs
614
+
615
+
616
+ def float16_expected_bits_binary(op: str, a_bits: int, b_bits: int) -> Tuple[int, bool]:
617
+ """Compute expected float16 bits for a binary op and whether it's NaN."""
618
+ a = float16_int_to_float(a_bits)
619
+ b = float16_int_to_float(b_bits)
620
+ a16 = torch.tensor(a, dtype=torch.float16)
621
+ b16 = torch.tensor(b, dtype=torch.float16)
622
+ if op == "add":
623
+ out = (a16 + b16).item()
624
+ elif op == "sub":
625
+ out = (a16 - b16).item()
626
+ elif op == "mul":
627
+ out = (a16 * b16).item()
628
+ elif op == "div":
629
+ out = (a16 / b16).item()
630
+ else:
631
+ raise ValueError(f"unknown op: {op}")
632
+ if out != out:
633
+ return 0x7E00, True
634
+ return float_to_int(float(out)), False
635
+
636
+
637
  # =============================================================================
638
  # BOOLEAN GATE TESTS
639
  # =============================================================================
 
2023
  """Test float16 arithmetic operations."""
2024
  results = []
2025
 
2026
+ rng = random.Random(0xF00D)
2027
+ light_pairs = build_float16_pairs(rng, 256)
2028
+ heavy_pairs = build_float16_pairs(rng, 128)
2029
+
2030
+ # Addition - randomized evaluation
2031
  if f"float16.add.exp_a_all_ones.weight" in ctx.tensors:
2032
  passed, total = 0, 0
2033
+ failures: List[Dict[str, Any]] = []
2034
+ gate_list = sorted([g for g in ctx.gates if g.startswith("float16.add.")])
 
 
 
 
 
2035
 
2036
+ for a_bits, b_bits in light_pairs:
2037
+ a_list = int_to_bits(a_bits, 16)
2038
+ b_list = int_to_bits(b_bits, 16)
2039
+ actual_bits = eval_prefix_outputs(ctx, "float16.add", {"a": a_list, "b": b_list}, gate_list=gate_list)
2040
+ actual_int = bits_to_int(actual_bits)
2041
+ expected_int, expected_nan = float16_expected_bits_binary("add", a_bits, b_bits)
2042
+ ok = float16_is_nan_bits(actual_int) if expected_nan else actual_int == expected_int
 
 
2043
 
2044
  total += 1
2045
+ if ok:
2046
+ passed += 1
2047
+ elif len(failures) < 10:
2048
+ failures.append({
2049
+ "a_bits": hex(a_bits),
2050
+ "b_bits": hex(b_bits),
2051
+ "expected": hex(expected_int),
2052
+ "actual": hex(actual_int),
2053
+ })
2054
 
2055
+ results.append(TestResult("float16.add", passed, total, failures))
2056
 
2057
+ # Subtraction - randomized evaluation
2058
  if f"float16.sub.b_neg_sign.weight" in ctx.tensors:
2059
  passed, total = 0, 0
2060
+ failures = []
2061
+ add_gate_list = sorted([g for g in ctx.gates if g.startswith("float16.add.")])
2062
+
2063
+ for a_bits, b_bits in light_pairs:
2064
+ a_list = int_to_bits(a_bits, 16)
2065
+ b_list = int_to_bits(b_bits, 16)
2066
+ # float16.sub is a wrapper over float16.add with inverted sign bit
2067
+ b_list_mod = list(b_list)
2068
+ b_list_mod[15] = 1.0 - b_list_mod[15]
2069
+ actual_bits = eval_prefix_outputs(ctx, "float16.add", {"a": a_list, "b": b_list_mod}, gate_list=add_gate_list)
2070
+ actual_int = bits_to_int(actual_bits)
2071
+ expected_int, expected_nan = float16_expected_bits_binary("sub", a_bits, b_bits)
2072
+ ok = float16_is_nan_bits(actual_int) if expected_nan else actual_int == expected_int
2073
+
2074
+ # Also validate the sign flip gate
2075
+ neg_sign = eval_gate_direct(ctx, "float16.sub.b_neg_sign", [b_list[15]])
2076
+ if neg_sign != (1.0 - b_list[15]):
2077
+ ok = False
2078
 
 
 
2079
  total += 1
2080
+ if ok:
2081
+ passed += 1
2082
+ elif len(failures) < 10:
2083
+ failures.append({
2084
+ "a_bits": hex(a_bits),
2085
+ "b_bits": hex(b_bits),
2086
+ "expected": hex(expected_int),
2087
+ "actual": hex(actual_int),
2088
+ })
2089
 
2090
+ results.append(TestResult("float16.sub", passed, total, failures))
2091
 
2092
+ # Multiplication - randomized evaluation
2093
  if f"float16.mul.exp_a_all_ones.weight" in ctx.tensors:
2094
  passed, total = 0, 0
2095
+ failures = []
2096
+ gate_list = sorted([g for g in ctx.gates if g.startswith("float16.mul.")])
 
 
 
2097
 
2098
+ for a_bits, b_bits in heavy_pairs:
2099
+ a_list = int_to_bits(a_bits, 16)
2100
+ b_list = int_to_bits(b_bits, 16)
2101
+ actual_bits = eval_prefix_outputs(ctx, "float16.mul", {"a": a_list, "b": b_list}, gate_list=gate_list)
2102
+ actual_int = bits_to_int(actual_bits)
2103
+ expected_int, expected_nan = float16_expected_bits_binary("mul", a_bits, b_bits)
2104
+ ok = float16_is_nan_bits(actual_int) if expected_nan else actual_int == expected_int
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2105
 
2106
  total += 1
2107
+ if ok:
2108
  passed += 1
2109
+ elif len(failures) < 10:
2110
+ failures.append({
2111
+ "a_bits": hex(a_bits),
2112
+ "b_bits": hex(b_bits),
2113
+ "expected": hex(expected_int),
2114
+ "actual": hex(actual_int),
2115
+ })
2116
 
2117
+ results.append(TestResult("float16.mul", passed, total, failures))
2118
 
2119
+ # Division - randomized evaluation
2120
  if f"float16.div.exp_a_all_ones.weight" in ctx.tensors:
2121
  passed, total = 0, 0
2122
+ failures = []
2123
+ gate_list = sorted([g for g in ctx.gates if g.startswith("float16.div.")])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2124
 
2125
+ for a_bits, b_bits in heavy_pairs:
2126
+ a_list = int_to_bits(a_bits, 16)
2127
+ b_list = int_to_bits(b_bits, 16)
2128
+ actual_bits = eval_prefix_outputs(ctx, "float16.div", {"a": a_list, "b": b_list}, gate_list=gate_list)
2129
+ actual_int = bits_to_int(actual_bits)
2130
+ expected_int, expected_nan = float16_expected_bits_binary("div", a_bits, b_bits)
2131
+ ok = float16_is_nan_bits(actual_int) if expected_nan else actual_int == expected_int
 
 
 
 
 
2132
 
2133
  total += 1
2134
+ if ok:
2135
  passed += 1
2136
+ elif len(failures) < 10:
2137
+ failures.append({
2138
+ "a_bits": hex(a_bits),
2139
+ "b_bits": hex(b_bits),
2140
+ "expected": hex(expected_int),
2141
+ "actual": hex(actual_int),
2142
+ })
2143
 
2144
+ results.append(TestResult("float16.div", passed, total, failures))
2145
 
2146
  return results
2147
 
 
2150
  """Test float16 conversion operations."""
2151
  results = []
2152
 
2153
+ rng = random.Random(0xC0DE)
2154
+
2155
  # toint
2156
  if f"float16.toint.exp_all_ones.weight" in ctx.tensors:
2157
  passed, total = 0, 0
2158
+ failures: List[Dict[str, Any]] = []
2159
+ gate_list = sorted([g for g in ctx.gates if g.startswith("float16.toint.")])
2160
 
2161
+ # Build deterministic input set: edge cases + filtered random patterns
2162
+ edge_vals = [
2163
+ 0x0000, 0x8000, 0x3C00, 0xBC00, 0x4000, 0xC000,
2164
+ 0x0400, 0x0001, 0x03FF, 0x3555, 0x3E00,
2165
+ ]
2166
+ test_bits = list(edge_vals)
2167
+ while len(test_bits) < 256:
2168
+ v = rng.getrandbits(16)
2169
+ if float16_is_nan_bits(v):
2170
+ continue
2171
+ test_bits.append(v)
2172
+
2173
+ for bits_int in test_bits:
2174
+ val = float16_int_to_float(bits_int)
2175
+ if val != val:
2176
+ continue
2177
+ if val == float('inf') or val == float('-inf'):
2178
+ continue
2179
+ expected = int(val)
2180
+ if expected < -32768 or expected > 32767:
2181
+ continue
2182
 
2183
+ bits = int_to_bits(bits_int, 16)
2184
+ actual_bits = eval_prefix_outputs(ctx, "float16.toint", {"x": bits}, gate_list=gate_list)
2185
+ actual = bits_to_int(actual_bits, signed=True)
 
2186
 
 
2187
  total += 1
2188
+ if actual == expected:
2189
+ passed += 1
2190
+ elif len(failures) < 10:
2191
+ failures.append({
2192
+ "in_bits": hex(bits_int),
2193
+ "expected": expected,
2194
+ "actual": actual,
2195
+ })
2196
 
2197
+ results.append(TestResult("float16.toint", passed, total, failures))
2198
 
2199
  # fromint
2200
  if f"float16.fromint.is_zero.weight" in ctx.tensors:
2201
  passed, total = 0, 0
2202
+ failures = []
2203
+ gate_list = sorted([g for g in ctx.gates if g.startswith("float16.fromint.")])
2204
 
2205
+ edge_ints = [0, 1, -1, 2, -2, 100, -100, 32767, -32768]
2206
+ test_vals = list(edge_ints)
2207
+ while len(test_vals) < 256:
2208
+ test_vals.append(rng.randint(-32768, 32767))
2209
 
2210
+ for val in test_vals:
2211
+ bits = int_to_bits(val, 16, signed=True)
2212
+ actual_bits = eval_prefix_outputs(ctx, "float16.fromint", {"x": bits}, gate_list=gate_list)
2213
+ actual_int = bits_to_int(actual_bits)
2214
 
2215
+ expected_bits = float_to_int(float(val))
2216
  total += 1
2217
+ if actual_int == expected_bits:
2218
+ passed += 1
2219
+ elif len(failures) < 10:
2220
+ failures.append({
2221
+ "in_val": val,
2222
+ "expected": hex(expected_bits),
2223
+ "actual": hex(actual_int),
2224
+ })
2225
+
2226
+ results.append(TestResult("float16.fromint", passed, total, failures))
2227
 
2228
  return results
2229