| --- |
| license: apache-2.0 |
| language: |
| - en |
| tags: |
| - threshold-logic |
| - arithmetic |
| - verified-computing |
| - neuromorphic |
| - digital-circuits |
| - frozen-weights |
| pipeline_tag: other |
| --- |
| |
| # Threshold Calculus |
|
|
| Digital circuits encoded as neural network weights. |
|
|
| Each gate is a threshold logic unit: `output = step(weights * inputs + bias)`. The step function fires when the weighted sum >= 0. This maps digital logic to tensor operations. |
|
|
| ## What's Here |
|
|
| | File | Description | |
| |------|-------------| |
| | `arithmetic.safetensors` | 626,374 tensors encoding 208,788 gates | |
| | `eval.py` | Test harness (211,581 tests) | |
| | `build.py` | Builds tensors and infers gate connectivity | |
|
|
| ## Circuits |
|
|
| **Float16 (IEEE 754)** |
| - `float16.add`, `float16.sub`, `float16.mul`, `float16.div` |
| - `float16.sqrt`, `float16.rsqrt`, `float16.pow` |
| - `float16.exp`, `float16.ln`, `float16.log2` |
| - `float16.sin`, `float16.cos`, `float16.tan`, `float16.tanh` |
| - `float16.neg`, `float16.abs`, `float16.cmp` |
| - `float16.toint`, `float16.fromint` |
| - `float16.pack`, `float16.unpack`, `float16.normalize` |
|
|
| Handles NaN, Inf, zero, subnormals. Mantissa alignment via barrel shifter. Normalization via CLZ. |
|
|
| Accuracy/rounding: |
| - Unary transcendental ops are LUT-backed over all 65,536 float16 inputs. |
| - Outputs match torch.float16 results (round-to-nearest-even); NaNs are canonicalized to 0x7E00. |
| - `float16.pow` is defined as exp(b * ln(a)) with float16 rounding at each stage. |
|
|
| **16-bit Integer** |
| - Adders: half, full, ripple carry (2/4/16 bit), add-with-carry (adc16bit) |
| - Subtraction: sub16bit, sbc16bit, neg16bit |
| - Comparison: cmp16bit, equality16bit |
| - Shifts: asr16bit, rol16bit, ror16bit |
| - CLZ: 16-bit |
|
|
| **Modular Arithmetic** |
| - mod2 through mod12 (divisibility testing) |
|
|
| **Boolean** |
| - AND, OR, NOT, NAND, NOR, XOR, XNOR, IMPLIES, BIIMPLIES |
|
|
| **Threshold** |
| - k-of-n gates (1-of-8 through 8-of-8) |
| - majority, minority, atleastk, atmostk, exactlyk |
|
|
| **Pattern Recognition** |
| - popcount, allzeros, allones, onehotdetector |
| - symmetry8bit, alternating8bit, hammingdistance8bit |
| - leadingones, trailingones, runlength |
|
|
| **Combinational** |
| - decoder3to8, encoder |
| - multiplexer (2/4/8 to 1), demultiplexer (1 to 2/4/8) |
| - barrelshifter8bit, priorityencoder8bit |
|
|
| ## How It Works |
|
|
| A threshold gate computes: |
|
|
| ``` |
| output = 1 if (w1*x1 + w2*x2 + ... + wn*xn + bias) >= 0 else 0 |
| ``` |
|
|
| This is a perceptron with Heaviside step activation. |
|
|
| **AND gate**: weights = [1, 1], bias = -1.5 |
| - (0,0): 0 + 0 - 1.5 = -1.5 < 0 -> 0 |
| - (0,1): 0 + 1 - 1.5 = -0.5 < 0 -> 0 |
| - (1,0): 1 + 0 - 1.5 = -0.5 < 0 -> 0 |
| - (1,1): 1 + 1 - 1.5 = 0.5 >= 0 -> 1 |
|
|
| **XOR** requires two layers (not linearly separable): |
| - Layer 1: OR and NAND in parallel |
| - Layer 2: AND of both outputs |
|
|
| ## Float16 Architecture (Short) |
|
|
| High-level dataflow: |
|
|
| ``` |
| float16.<op> |
| a,b -> unpack -> classify -> core op -> normalize/round -> pack -> out |
| ``` |
|
|
| Step-by-step (condensed): |
| 1) Unpack sign/exponent/mantissa. Subnormals use implicit 0, normals use implicit 1. |
| 2) Classify inputs: zero, subnormal, normal, inf, NaN. |
| 3) Core op: |
| - add/sub: align exponents, add/sub mantissas, compute sign. |
| - mul/div: add/sub exponents (minus bias), multiply/divide mantissas. |
| - unary LUT: lookup output for each 16-bit input (torch.float16), with NaN canonicalization. |
| - pow: ln(a) -> mul(b, ln(a)) -> exp, rounded at each stage. |
| 4) Normalize and round-to-nearest-even (CLZ + shifts). |
| 5) Pack sign/exponent/mantissa and mux special cases (NaN/Inf/zero). |
|
|
| ## Self-Documenting Format |
|
|
| Each gate has three tensors in `arithmetic.safetensors`: |
| - `.weight` -- input weights |
| - `.bias` -- threshold |
| - `.inputs` -- int64 tensor of signal IDs (ordered to match `.weight`) |
|
|
| Signal registry in metadata maps IDs to names: |
|
|
| ```python |
| from safetensors import safe_open |
| import json |
| |
| with safe_open('arithmetic.safetensors', framework='pt') as f: |
| registry = json.loads(f.metadata()['signal_registry']) |
| inputs = f.get_tensor('boolean.and.inputs') |
| names = [registry[str(i.item())] for i in inputs] |
| # ['$a', '$b'] |
| ``` |
|
|
| Signal naming: |
| - `$name` -- circuit input (e.g., `$a`, `$dividend[0]`) |
| - `#0`, `#1` -- constants |
| - `gate.path` -- output of another gate |
|
|
| Format details: |
| - Metadata includes `signal_registry` (JSON map from ID to name) and `format_version` (currently `2.0`). |
| - `.inputs` stores global signal IDs; these IDs are resolved through `signal_registry`. |
| - External inputs are names starting with `$` or containing `.$` (e.g., `float16.add.$a[3]`). |
| - All gates include `.inputs`; `build.py` infers them and `--inputs-coverage` fails if resolution is missing. |
|
|
| ## How to Reproduce |
|
|
| Rebuild tensors: |
|
|
| ```bash |
| python build.py |
| ``` |
|
|
| Run full evaluation (always full + verbose): |
|
|
| ```bash |
| python eval.py |
| ``` |
|
|
| Run coverage and input-routing validation: |
|
|
| ```bash |
| python eval.py --coverage --inputs-coverage |
| ``` |
|
|
| Expected runtimes (ballpark, CPU dependent): |
| - `build.py`: ~1-2 minutes, produces ~247 MB `arithmetic.safetensors` |
| - `eval.py --coverage --inputs-coverage`: ~3-4 minutes for 211,581 tests |
|
|
| ## Running Eval |
|
|
| ```bash |
| python eval.py |
| ``` |
|
|
| Tests all circuits. Small circuits are exhaustive; 16-bit arithmetic is sampled on grids (plus edge cases). Float16 tests cover special cases (NaN, Inf, +/-0, subnormals) plus normal arithmetic. |
| Eval runs full + verbose by default; there is no quick/verbose mode. Use --circuit to filter reported circuits. |
|
|
| For coverage and input-routing validation: |
|
|
| ```bash |
| python eval.py --coverage --inputs-coverage |
| ``` |
|
|
| `--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. |
|
|
| ## Python Calculator Interface (Gate-Level) |
|
|
| `calculator.py` provides a pure gate-level evaluator that uses only `.inputs` + `.weight`/`.bias` (no arithmetic shortcuts). This is intended as a rigorous proof-of-concept for using the weights as a calculator. |
| Expression mode routes even constants through a circuit (via `float16.add` with +0) to ensure gate-level evaluation. |
|
|
| Examples: |
|
|
| ```bash |
| python calculator.py float16.add 1.0 2.0 |
| python calculator.py float16.sqrt 2.0 |
| python calculator.py float16.add --inputs a=0x3c00 b=0x4000 --hex |
| python calculator.py --expr "1 + 1" |
| python calculator.py "sin(pi / 2)" |
| python calculator.py --expr "1 + 1" --json |
| python calculator.py "pi" --strict |
| ``` |
|
|
| Programmatic use: |
|
|
| ```python |
| from calculator import ThresholdCalculator |
| |
| calc = ThresholdCalculator("arithmetic.safetensors") |
| out, _ = calc.float16_binop("add", 1.0, 2.0) |
| print(out) |
| ``` |
|
|
| ## Development History |
|
|
| 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. |
|
|
| Float16 was added later. The commit history shows the iterative process--float16.add went through multiple rounds of bug fixes for edge cases (zero handling, sign logic, normalization). Mul and div required multi-bit carry infrastructure. |
|
|
| ## Project Origin |
|
|
| This began as an attempt to build a complete threshold-logic CPU. The CPU is in a separate repo (phanerozoic/8bit-threshold-computer). This repo focuses on the arithmetic core. |
|
|
| ## Roadmap |
|
|
| **Done:** |
| - Float16 core (add/sub/mul/div) |
| - Float16 utilities (pack/unpack/normalize/conversions) |
| - Float16 IEEE-754 half compliance for add/sub/mul/div + toint/fromint (including subnormals) |
| - Float16 unary LUTs (sqrt/rsqrt/exp/ln/log2/log10/sin/cos/tan/tanh/asin/acos/atan/sinh/cosh/floor/ceil/round) |
| - Float16 pow via exp(b * ln(a)) |
| - 16-bit integer arithmetic (add/sub/cmp/shifts/CLZ) |
| - Boolean, threshold, modular, pattern recognition, combinational |
|
|
| **Next:** |
| - Add 32-bit integer arithmetic circuits (add/sub/shift/compare, then mul/div). |
| - Add higher precision modes (float32 or fixed-point) for typical calculator accuracy. |
|
|
| **Cleanup:** |
| - None (8-bit arithmetic scaffolding removed) |
|
|
| ## TODO (Unified) |
|
|
| - 32-bit integer circuits and an int32 calculator mode. |
| - Degree-mode trig and implicit multiplication parsing. |
| - Higher-precision arithmetic (float32 or fixed-point). |
| - Complex-number support (or explicit domain errors). |
|
|
| ## Hugging Face Space (Proof of Concept) |
|
|
| A minimal Space UI is included in `app.py`. It uses the gate-level evaluator only (no arithmetic shortcuts) and exposes a normal calculator-style expression input. |
|
|
| Example expressions: |
| - `1 + 1` |
| - `sin(pi / 2)` |
| - `exp(ln(2))` |
|
|
| Notes: |
| - All results are **float16** (IEEE-754 half) and may be rounded. |
| - `pow` uses the `exp(b*ln(a))` definition; negative bases yield NaN. |
|
|
| ## License |
|
|
| Apache 2.0 |
|
|