Skip to content

D-U9-ABACUS: schoolbook U9 x U9 with carry escalation and power-of-two anchors (probe) - #389

Merged
AdaWorldAPI merged 13 commits into
masterfrom
claude/u9-abacus
Oct 29, 2026
Merged

AdaWorldAPI merged 13 commits into
masterfrom
claude/u9-abacus

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

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::simd facade 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's f32 accumulation. Each lane keeps two numbers:

  • low, a wrapping u16;
  • 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 in high (1 × 65,536). The carry out of each 16-bit add moves into high as 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:

  • react: add first, then read the carry out of the sum, msb((low & p) | ((low | p) & !sum));
  • anticipate: before adding, check whether p is 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:

VNNI AVX-512, no VNNI AVX2
A' arithmetic (f32) 6.34 – 6.50 6.28 – 6.69 10.7 – 11.0
D react arithmetic 5.20 – 5.70 5.36 – 5.64 21.2 – 24.3
D anticipate arithmetic 5.94 – 6.45 5.98 – 6.31 9.89 – 10.1
B arithmetic (#388) 2.39 – 2.41 5.02 – 5.03 8.95 – 9.17
A' / D react end-to-end 7.62 – 7.83 / 6.62 – 7.25 7.83 / 6.77 – 6.80 81.1 – 88.7 / 79.6 – 90.1
B end-to-end 2.89 – 2.91 5.62 10.0 – 10.1

What the numbers show:

  • The carry column beats the f32 path in L1. On AVX-512, D react is about 10% faster than A'. Streamed from memory, their arithmetic-only rows are level (both memory-bound), and D react end to end is faster (11–15 vs 18 ns on VNNI).
  • Which carry rule wins depends on the tier. On AVX2, anticipate is faster, because LLVM's AVX2 code for react is longer (16 vmovq, 6 vpblendd).
  • B stays ahead, and accumulation is not the reason. D still has to widen each byte to 16 bits and do 4 vpmullw before it can add anything, while B multiplies directly from bytes into 32-bit sums.
  • AVX2 end-to-end numbers measure something else. On AVX2, A's routing step (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 residual low7, and computes the 128 term as a masked sum of a shifted left by 7:

a·|b| = a·low7 + (Σ a over the lanes where bit 7 of b is set) << 7

E3 forms that masked sum with one masked byte-dot of a against +1/-1 bytes (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:

VNNI AVX-512, no VNNI AVX2
B end-to-end / arithmetic only 2.81 – 2.89 / 2.34 – 2.41 5.62 / 5.02 10.0 – 10.1 / 9.17 – 9.23
E3 end-to-end / arithmetic only 2.84 – 2.92 / 2.28 5.52 / 4.94 – 4.95 37.6 – 43.6 / 37.5 – 45.3
E4 end-to-end (4 dots, second against all-ones bytes) 3.47 – 3.56 6.47 – 6.48 14.5
Esum end-to-end (sum_bytes_u64, reduced every block) 4.33 – 4.44 5.95 61.2 – 66.8

What the numbers show:

  • E3 removes one byte-dot but spends about the same building the +1/-1 vector. It ties B end-to-end on AVX-512 and is about 4% faster in arithmetic alone.
  • AVX2 loses because the +1/-1 vector goes through U8x64::mask_blend, which runs one byte at a time there.
  • E4 and Esum are slower on every tier.

Verdict: no adoption. Two missing facade words would change the outcome:

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_save3 is an exact full adder (3 xor + 2 and) and is kept as ADOPT-LAB. As a replacement for B, C is refused:

  • the best variant (one popcount per partial-product plane) costs 12.4 ns of arithmetic on VNNI even with both operands' planes prepared for free, against B's 2.9 ns end to end;
  • the carry-save folds cost about 290 ns, because LLVM leaves them as scalar loops over arrays in memory;
  • without the hardware popcount instruction (v4, AVX2), count_ones compiles to a vpshufb byte shuffle;
  • reusing prepared operands never breaks even (plane extraction for both operands is 3.5 ns on VNNI).

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 Block is built in registers. The first version streamed a padded Vec<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 i64 reference and against B:

  • all 511 × 511 pairs;
  • full mixed blocks;
  • all-255 runs past every flush limit;
  • never-touched refused, touched zero scoring 0.

Additional checks:

  • C: the full adder bit by bit, plane extraction for every magnitude, and carries rippling through many columns.
  • D: both carry rules against u32 arithmetic for every low in 0..=65535, with p at, just above and just below the headroom.
  • E: the 128 split for every magnitude, and the +1/-1 sign 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

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
@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9d955919-f2e1-49f2-9ffe-b22b619d39c1
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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
@AdaWorldAPI AdaWorldAPI changed the title D-U9-ABACUS: schoolbook U9 x U9 on bit planes with carry-save columns (probe) D-U9-ABACUS: schoolbook U9 x U9 with carry escalation, bit planes and a 16-bit carry column (probe) Oct 29, 2026
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
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
@AdaWorldAPI AdaWorldAPI changed the title D-U9-ABACUS: schoolbook U9 x U9 with carry escalation, bit planes and a 16-bit carry column (probe) D-U9-ABACUS: schoolbook U9 x U9 with carry escalation and power-of-two anchors (probe) Oct 29, 2026
@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review October 29, 2026 21:11
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-29T21:13:38.507130Z 9b2c87a Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread examples/u9_abacus/main.rs Resolved
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
@AdaWorldAPI
AdaWorldAPI changed the base branch from claude/u9-head-to-head to master October 29, 2026 22:13
@AdaWorldAPI
AdaWorldAPI merged commit 6812618 into master Oct 29, 2026
31 checks passed
AdaWorldAPI pushed a commit that referenced this pull request Oct 30, 2026
…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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants