Repository navigation
D-U9-ABACUS: schoolbook U9 x U9 with carry escalation and power-of-two anchors (probe) - #389
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
carry_save3 is an exact five-operation full adder that LLVM lowers cleanly and is kept as a lab primitive. As a replacement for #388's B kernel the byte-abacus is refused: the best fold is 5x B's arithmetic with planes prepared for free, the carry-save folds are about 120x because LLVM keeps them as scalar loops over memory, and there is no break-even for prepared operands on any tier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars.
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
React-carry D is about 10 % faster than A' on AVX-512, where LLVM folds the AND/XOR carry rule into two vpternlogq per carry; anticipate wins on AVX2. B stays ahead because D still widens u8 to u16 before multiplying. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
The report's top verdict read as if the whole abacus idea were refused. Only C (bit-plane multiplication) is; D (the 64K carry boundary on U16 products) is a separate experiment with its own, positive verdict. Adds the sign-routing overhead for D, A' and B. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
Splitting |b| at 128 and taking the anchor term as a shifted masked sum saves one byte-dot (E3), but building the +1/-1 sign vector costs about as much: E3 ties B on AVX-512 VNNI and is slower on AVX2. Records the two facade words that would change the result. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b2c87ab39
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
# Conflicts: # .claude/blackboard.md
The timed loops streamed padded Vec<Block> arrays (128 B per 72-B block) and the plane-extraction row read four of sixteen planes, so the optimizer could drop the rest. Operands now use the shared compact layout and both plane arrays pass through black_box. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
Every table is re-measured on three tiers after the layout and plane-extraction fixes. Plane extraction on VNNI L1 is 3.5 ns (was 2.5, twelve planes had been dropped); streamed rows fall with the compact layout. Streamed, D react's arithmetic-only row is now level with A' rather than faster; every verdict is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v
…y for U32x16 shifts half_add_mask(a, carry) = (a ^ carry, a & carry) and full_add_mask(a, b, c) = (a ^ b ^ c, majority) are the flip and carry_save3 laws proven in #391 and #389, now in simd_masking_ops and re-exported through ndarray::simd. Each u64 is one significance column of 64 independent counters; the carry belongs one column up in the same lane. Tests: both truth tables in every lane position, lanes 0/31/32/63 alone, 2000 random words (with zero, all-ones and alternating masks) against per-lane addition, and a ripple counter that keeps lanes apart while moving the carry to a neighbouring bit corrupts it. The wasm parity harness now also checks U32x16 << and >> with a distinct count per lane. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY
Summary
This probe tests two exact ways to accumulate U9 × U9 products by carrying into the next place value instead of widening every operand up front. Both are compared against #388 (merged) on the same data and the same 64-bit reference. Everything is written in plain Rust, using existing
ndarray::simdfacade words and ordinary bit operations. Nothing changes in the backends, the facade, Codec9 or the storage format.Candidate D: a 16-bit place-value column (
carry16.rs)D keeps #388's A' multiplication as it is (
U16x32::mullo, with the sign routed by the polarity XOR) and replaces only A'sf32accumulation. Each lane keeps two numbers:low, a wrappingu16;high, counting units of 2^16.The lane's value is
low + 2^16·high. For example, 67,025 is stored as low 1,489 plus one unit inhigh(1 × 65,536). The carry out of each 16-bit add moves intohighas one unit. Carries are counted, never XORed together, because two carries mean two units. The carry is computed with pure bit logic, in one of two ways:msb((low & p) | ((low | p) & !sum));pis larger than the remaining headroom!low, using the borrow out of!low - p.On AVX-512, LLVM compiles each carry rule into
vpternlogq(2 per carry for react, 3 for anticipate). There are no shuffles and no floats.Results in ns per 64 values, L1-resident, ranges over 3 runs:
What the numbers show:
vmovq, 6vpblendd).vpmullwbefore it can add anything, while B multiplies directly from bytes into 32-bit sums.U8x64::mask_blend) runs one byte at a time, and that dominates both A' and D end to end.Verdict for D: ADOPT-LAB as the exact replacement for A's f32 accumulation. DEFER as a replacement for B.
Candidate E: split at the power of two 128 (
anchor.rs)No U9 operand ever reaches 64K, but every operand has a known power-of-two boundary at 128 (bit 7). B already splits there:
|b| = low7 + 128·bit7. E multiplies only the residuallow7, and computes the 128 term as a masked sum ofashifted left by 7:a·|b| = a·low7 + (Σ a over the lanes where bit 7 of b is set) << 7E3 forms that masked sum with one masked byte-dot of
aagainst+1/-1bytes (one byte per lane, from the sign plane, gated by bit 7). That is 3 byte-dots per block instead of B's 4.Results in ns per 64 values, L1-resident:
sum_bytes_u64, reduced every block)What the numbers show:
+1/-1vector. It ties B end-to-end on AVX-512 and is about 4% faster in arithmetic alone.+1/-1vector goes throughU8x64::mask_blend, which runs one byte at a time there.Verdict: no adoption. Two missing facade words would change the outcome:
vpsadbwinto aU64x8accumulator, reduced only at flush), now proposed as Add U64x8::sum8_u8_add_masked, a vertical masked byte sum #390;+1/-1bytes that does not go throughmask_blend(recorded, not added).While checking E's disassembly I found that the facade's AVX2 masked byte-dot contains 4
vpshufb. #388's "no shuffle in B" is therefore scoped to AVX-512.Candidate C: bit-plane schoolbook (
abacus.rs)C computes
a·b = Σ (a_i AND b_j)·2^(i+j)over eight bit planes per block.carry_save3is an exact full adder (3xor+ 2and) and is kept as ADOPT-LAB. As a replacement for B, C is refused:count_onescompiles to avpshufbbyte shuffle;Measurement method (changed after review)
The timed operands use #388's compact layout: 64 magnitude bytes and one plane per block, in two arrays (72 bytes per block). Each
Blockis built in registers. The first version streamed a paddedVec<Block>(128 bytes per block), and its plane-extraction row kept only 4 of 16 planes, so the compiler could drop the rest. Both were fixed after Codex's review and every table was re-measured on three tiers. The verdicts did not change.Correctness
All candidates are checked against #388's
i64reference and against B:Additional checks:
u32arithmetic for everylowin 0..=65535, withpat, just above and just below the headroom.+1/-1sign bytes.The tests pass on native, v4, v3, AVX (qemu), v2 (qemu), aarch64 (qemu) and nightly-simd. I broke 19 checks on purpose (9 for C, 5 for D, 5 for E), and the tests failed every time. Clippy, rustfmt and the shuffle guard are clean.
The full tables, including streamed and scan rows, and the disassembly are in
.claude/knowledge/u9-abacus-probe.md.🤖 Generated with Claude Code
https://claude.ai/code/session_01XAqKMMjKnig6MXTd41Lf2v