CharlesCNorton commited on
Commit ·
10e432e
1
Parent(s): e669ef2
Add randomized float16 tests and document open issues
Browse files
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1905 |
if f"float16.add.exp_a_all_ones.weight" in ctx.tensors:
|
| 1906 |
passed, total = 0, 0
|
| 1907 |
-
|
| 1908 |
-
|
| 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
|
| 1916 |
-
|
| 1917 |
-
|
| 1918 |
-
|
| 1919 |
-
|
| 1920 |
-
|
| 1921 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1934 |
|
| 1935 |
-
for a, b in test_pairs:
|
| 1936 |
-
ctx.tested_tensors.add("float16.sub.b_neg_sign.weight")
|
| 1937 |
total += 1
|
| 1938 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1939 |
|
| 1940 |
-
results.append(TestResult("float16.sub", passed, total))
|
| 1941 |
|
| 1942 |
-
# Multiplication -
|
| 1943 |
if f"float16.mul.exp_a_all_ones.weight" in ctx.tensors:
|
| 1944 |
passed, total = 0, 0
|
| 1945 |
-
|
| 1946 |
-
|
| 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
|
| 1952 |
-
|
| 1953 |
-
|
| 1954 |
-
|
| 1955 |
-
|
| 1956 |
-
|
| 1957 |
-
|
| 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
|
| 2005 |
passed += 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2006 |
|
| 2007 |
-
results.append(TestResult("float16.mul", passed, total))
|
| 2008 |
|
| 2009 |
-
# Division -
|
| 2010 |
if f"float16.div.exp_a_all_ones.weight" in ctx.tensors:
|
| 2011 |
passed, total = 0, 0
|
| 2012 |
-
|
| 2013 |
-
|
| 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 |
-
|
| 2058 |
-
|
| 2059 |
-
|
| 2060 |
-
|
| 2061 |
-
|
| 2062 |
-
|
| 2063 |
-
|
| 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
|
| 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 |
-
|
|
|
|
| 2087 |
|
| 2088 |
-
|
| 2089 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2090 |
|
| 2091 |
-
|
| 2092 |
-
|
| 2093 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
| 2106 |
|
| 2107 |
-
|
| 2108 |
-
|
|
|
|
|
|
|
| 2109 |
|
| 2110 |
-
|
| 2111 |
-
|
| 2112 |
-
|
| 2113 |
-
|
| 2114 |
|
|
|
|
| 2115 |
total += 1
|
| 2116 |
-
|
| 2117 |
-
|
| 2118 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
|