diff --git a/.claude/blackboard.md b/.claude/blackboard.md index a9b1b83d..07c79c65 100644 --- a/.claude/blackboard.md +++ b/.claude/blackboard.md @@ -3957,3 +3957,21 @@ quarter; NEON uaddlp/uadalp; wasm extadd_pairwise; scalar; nightly SWAR). Parity (0x506/0x507); 7 tiers green; 7 disable runs red. 0.687 ns/64 bytes on AVX-512 (281-285x the reference loop), 1.99 ns on AVX2 (98x). Knowledge: `turbovec9-u9-substrate.md` § second facade word. Not yet wired into the abacus probe (#389). +## U9 arc closeout: open fruit (2026-10-29, after #388-#391 merged) + +Closed: #388 (B wins, contract recorded), #389 (C refused, D adopt-lab, E no +adoption), #390 (`sum8_u8_add_masked`), #391 (F adopt-lab, P/L/PL refused). +Every Codex finding fixed and re-measured. Excluded, do not reopen without new +evidence: bit-plane multiplication as a B replacement; lazy EMPTY/FULL flags +in bit-sliced counters. + +| # | fruit | size | gate | +|---|---|---|---| +| U1 | wire `sum8_u8_add_masked` into candidate E (Esum's anchor term) and re-measure E against B | low | E's oracle tests unchanged; one row per tier | +| U2 | AVX2 streamed B is 27 - 33 ns against 9 - 10 in L1, more than the extra bytes explain | low | one probe that separates bandwidth from the masked byte-dot's 4 `vpshufb` | +| U3 | NEON and wasm timings for B, D and F (only x86 measured) | low | a real aarch64 host; qemu numbers are not valid | +| U4 | sign plane to `+1/-1` bytes without `mask_blend` (E3's cost on AVX2) | high | facade word with parity on all realizations; must pass the facet-stride guard | +| U5 | AVX2 `U8x64::mask_blend` is a per-lane loop and dominates every A/E3 row there | high | facet-stride R2 burn-down item; parity unchanged | +| U6 | `U8x64 -> I8x64` bit view for B's `low7`/`top` (today a store/load) | heavy | needs an operator ruling: facet-stride R3 forbids u8/i8 mixing | +| U7 | recall on a real TurboVec-U9 index (arithmetic parity says nothing about retrieval) | heavy | owner experiment, defined in the head-to-head report | + diff --git a/.claude/board/EPIPHANIES.md b/.claude/board/EPIPHANIES.md index ecb41724..ffde88d0 100644 --- a/.claude/board/EPIPHANIES.md +++ b/.claude/board/EPIPHANIES.md @@ -1,5 +1,58 @@ # ndarray — Epiphanies (append-only) +## 2026-10-29 — Signed magnitude is schoolbook arithmetic: sign routes, the split multiplies, the fold accumulates +**Status:** FINDING (measured on 3 x86 tiers; exact on 7 realizations) +**Scope:** @savant-architect @simd-savant @vector-synthesis domain:dr512 domain:u9 +**Cross-ref:** PRs #388, #389, #390 (merged); `.claude/knowledge/turbovec9-u9-head-to-head.md` +§ arithmetic contract; `.claude/knowledge/u9-abacus-probe.md` + +A U9 value on the DR512 8+1 block is an unsigned magnitude plus a presence +bit. Exact signed multiply-accumulate needs no float, no wider operand and no +negation: + +- **Sign is a routing decision.** `negative = plane_a ^ plane_b`; it selects + which masked accumulator a product enters. It is never multiplied in. +- **Multiplication is an exact decomposition.** `low7 = |b| & 0x7F`, + `top = |b| ^ low7`, so `|b| = low7 - top` with `top` read as a signed byte + (0 or -128). Both halves fit the U8 x I8 byte-dot exactly. +- **Accumulation is a fold.** `(pos_low7 - pos_top) - (neg_low7 - neg_top)`, + flushed into `i64` before an `i32` lane can overflow. + +The reflex this replaces is "U9 does not fit a byte, so widen to U16, and when +that gets messy go to f32". That route (A) costs 7.6 - 7.8 ns per 64 values on +AVX-512 VNNI and must flush every 256 blocks to keep its f32 sums below 2^24, +where f32 stops being exact; the decomposition (B) costs 3.05 ns and flushes +every 16,384 blocks, below `i32::MAX`. On AVX2 A is 75 - 81 ns, because the +routing goes through a per-lane `mask_blend`. + +Consequence for future work: any signed integer format stored as magnitude plus +presence should first be expressed as split + route + fold over the existing +byte-dot, before a wider type or a new kernel is proposed. Measured exceptions +so far: none (bit planes, the 128 anchor and the 16-bit column all lost to B). + +## 2026-10-29 — Exactness past a lane's width is a carry count, not a wider type +**Status:** FINDING (measured; LLVM lowering read from the disassembly) +**Scope:** @savant-architect @simd-savant domain:u9 domain:accumulation +**Cross-ref:** PRs #389, #391 (merged); `.claude/knowledge/u9-abacus-probe.md` +§ candidate D; `.claude/knowledge/u9-domino-probe.md` + +A u16 product stream can be summed exactly without widening each lane: keep a +wrapping 16-bit `low` and count carries into `high` (`value = low + 2^16 * +high`). The carry is pure bit logic, either read after the add, +`msb((low & p) | ((low | p) & !sum))`, or predicted before it, the borrow of +`!low - p`. On AVX-512 LLVM lowers each rule to two or three `vpternlogq`, with +no shuffle and no float. This replaces A's f32 accumulation at about 10 % less +cost in L1 (5.2 - 5.7 vs 6.3 - 6.5 ns per 64 on VNNI). + +Two limits, both measured: + +- Carries are counted, never XORed together: two carries are two units. +- Making the carry lazier does not pay. In a bit-sliced counter, flags that + skip work on EMPTY or FULL columns cost 1.6 - 2.2 ns per column against + 0.5 ns for the eager XOR/AND ripple they replace, because each flag is a + branch plus a `u128` test. The eager ripple (F) is the only bit-plane + counter worth keeping. + ## 2026-09-17 — On a tree-path register the metric is lzcnt, popcount is the tie-break, and little-endian bytes need one bswap first **Status:** FINDING (the layout and the instruction facts) + NAMED GAP (G8, not built) **Scope:** @simd-savant @family-codec-smith domain:masking-ops domain:v3-facet