From 7100daf5865f5346ce153caeb87675e938ef421c Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 23:18:07 +0000 Subject: [PATCH] knowledge: revise synergy map after #385-#393 and lance-graph #1847-#1848 Adds section R2. Rows moved: H1 partly closed by #385 (first U16x32 parity found two AVX-512 bugs); H3 reframed (count_ones lowers to the same vpshufb nibble table on AVX2, so a rewrite would only hide it); AVX2 mask_blend set as R2 burn-down item #1 by measurement. New dots N10-N15: the two 9-bit contracts agree across repos; recall and cost were measured on different layouts; the AVX2 masked dot hides compiler-emitted shuffles the source guard cannot see; W4's ndarray half shipped in #386; the u8->i8 view and R3 are one ruling; NEON and wasm lib tests are not in CI. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01XAqKMPh2Seh2WLccCksAdY --- .claude/knowledge/synergy-map-2026-10-29.md | 124 ++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/.claude/knowledge/synergy-map-2026-10-29.md b/.claude/knowledge/synergy-map-2026-10-29.md index a02b46d5..355be1b8 100644 --- a/.claude/knowledge/synergy-map-2026-10-29.md +++ b/.claude/knowledge/synergy-map-2026-10-29.md @@ -20,6 +20,130 @@ point of it and adds what the merges since then closed or opened. LOOSE below rests on a line that was re-read on the main thread; rows marked "not re-read" are carried over unchanged. +## R2. Revision after ndarray #385–#393 and lance-graph #1847–#1848 + +Second pass, same day. ndarray `master` `870e733` (9 PRs, 39 files, ++8,597/−31). lance-graph `main` `61720fa` (2 PRs, docs only: the +`palette512-turbovec9-nsl-worksteal` board entry, the DR³ session +scratchpad, the epistemic-basin scratchpad). Sections 0–7 below are the +first pass and stay as written; this section revises their rows. The +facet-stride counts were re-taken: still 166 (7 R1, 157 R2, 2 R3). The new +code added no hit, and its probe that times shuffle lookups on purpose +carries a reasoned R1 exclusion. + +### R2.1 What the nine ndarray PRs did + +| PR | added | verdict recorded | +|---|---|---| +| #385 | `U16x32`: `zero`, `simd_min/max`, `saturating_add/sub`, `cmpeq/cmpgt_mask`, `reduce_min/max`, `to_f32x16_lo/hi` on AVX-512, AVX2, AVX, scalar, nightly; `reduce_sum` → `u16` everywhere; parity module `u16x32_facade_parity` | first parity coverage for the type found two older AVX-512 bugs: `shl`/`shr` returned 0 except for counts 1, 2, 4 and 8; `pack_saturate_u8` used `packus_epi16`. Both fixed | +| #386 | `I32x16::dot4_u8_i8_add` (`vpdpbusd` with VNNI, widen-then-madd otherwise), `U8x64::low_nibbles/high_nibbles` (GFNI when compiled in), every backend; contract note "`vpmaddubsw` is NOT an emulation of `vpdpbusd`" | triggered by turbovec's raw `vpdpbusd`/`SDOT` kernels | +| #387 | `I32x16::dot4_u8_i8_add_masked`; crate-private `BIT_IN_BYTE`, `mask_byte_spread`; `u9_cost_probe` | stored SM9 grid 46.0 dB at 1.29–1.32 ns/64; 512 Lloyd-Max IDs 49.9 dB but the in-scan lookup costs ~5× the arithmetic; no 512-entry facade word | +| #388 | head-to-head probe | B (U8×I8 byte-dot) wins every row; contract split Arithmetic511 vs Codebook512 | +| #389 | abacus probe | C (bit planes) REFUSE; D (16-bit carry column) ADOPT-LAB as A's f32 replacement; E (anchor at 128) no adoption; `carry_save3` ADOPT-LAB | +| #390 | `U64x8::sum8_u8_add_masked`, every backend | 0.687 ns/64 B on AVX-512, 1.99 ns on AVX2 | +| #391 | domino probe | F (eager flip) ADOPT-LAB; P, L, PL REFUSE | +| #392 | two EPIPHANIES entries; U1–U7 fruit list | — | +| #393 | E5 probe | ADOPT-LAB only on AVX-512 without VNNI; DEFER as B replacement; closes U1 | + +### R2.2 Rows of this map that moved + +| row | was | now | evidence | +|---|---|---|---| +| H1 parity for 16 unnamed types | LOOSE | **PARTLY CLOSED, and its value is now measured** | #385 covered `U16x32` in `src/simd.rs` test modules and found two real AVX-512 bugs on the first type tried. The 16-type count in `simd-masking-parity` is unchanged (the new tests live in lib test modules) | +| H3 AVX2 popcount: scalar `popcnt` vs nibble table | LOOSE | **REFRAMED** | `u9-abacus-probe.md:275`: "Without VPOPCNTDQ the popcount fold is a byte shuffle. On v4 and AVX2, `u64::count_ones` lowers to the `vpshufb` nibble table". Rewriting the 7 R1 hits as `count_ones` would silence the source-level guard and leave the machine code identical. The honest options are an exclusion with a measured reason, or a per-u64 `popcnt` that LLVM is prevented from vectorizing, which is a measurement question, not an edit | +| N6 D-RPF-4 "LUT-weighted strided sum" | PROBE | PROBE, sharpened | #387 measured per-position table reads (`lut_u8` shape) at 38–40 ns/64 against ~1.3 ns for the arithmetic they feed. A LUT-weighted sum built from `lut_u8` is therefore lookup-bound; the probe must time the LUT step separately | +| N7/N8 burn-down | one job | **priority set by measurement** | `U8x64::mask_blend`'s AVX2 realization is a per-lane loop (an R2 hit) and dominates every A/E3 row on AVX2: 75–81 ns vs B 10 ns per 64 (`turbovec9-u9-head-to-head.md:227`). Fix it first of the 157 R2 hits | +| N4 runtime-scale batch Codec9 | ruling needed | ruling needed, **second requester** | lance-graph W3 compares f32/DR512/i4/U9 graph loss and gradient; DR512 values for that come from a runtime-calibrated encode, the same need NSL stated | +| L4 `lut9` doc | LOOSE | LOOSE | `simd_stride.rs` untouched by the delta | +| H2 aarch64 guard | LOOSE | LOOSE | `src/cpu_guard.rs:318` and `:461-465` unchanged (the path in my census brief was wrong; the file is `src/cpu_guard.rs`) | +| H5 NaN erasers | LOOSE | LOOSE | 14 references in ndarray; 4 in lance-graph, all in `bgz-tensor/src/{adaptive_codec,had_cascade}.rs` | +| L5 runtime-detect census | LOOSE | LOOSE | 96 sites in 17 files, unchanged | + +### R2.3 New dots + +**N10 — two 9-bit contracts, kept apart on both sides.** ndarray's +head-to-head (`:244-250`) and lance-graph's entry (`:22-24`, scratchpad +`:21`) record the same split: Arithmetic511 (DR512: sign + magnitude, one +never-touched code) and Codebook512 (TurboVec-U9: 512 centroid IDs, all +valid). Both say never reinterpret one as the other; lance-graph adds "never +silently turn `lut9` into a generic U9 lookup" (`:34-35`), and ndarray added +no 512-entry facade word. Consistent; nothing to do. + +**N11 — the two repos measured different things and point opposite ways.** +lance-graph's TurboVec9 numbers are recall: scalar i9 0.984 vs i4 0.788 +Recall@10 on synthetic unit vectors (`:518`), using 512 Lloyd-Max centroid +IDs. ndarray's numbers are cost and signal: the IDs reach 49.9 dB but need a +512-entry lookup in the scan (5.4–5.7 ns/64); a stored uniform 9-bit grid +reaches 46.0 dB at 1.3 ns/64, the same per-dimension L1 cost as i4 +(`turbovec9-u9-substrate.md:111-123`). Neither side measured recall for the +stored-grid layout. The owner experiment U7 (and lance-graph W1/W6) should +include the stored SM9 arm next to the ID arm, or the faster layout is never +compared on the metric that decides. + +**N12 — the masked byte-dot hides shuffles on AVX2.** +`dot4_u8_i8_add_masked` on AVX2 lowers to 4 `vpshufb` (LLVM's spread of the +mask), and E5 on AVX2 to 8 (`u9-e5-probe.md:111`). The facet-stride guard +reads source, so it cannot see this. U2 (AVX2 streamed B 27–33 ns vs 9–10 in +L1) may be these shuffles rather than bandwidth; that is exactly the probe +U2 names. `scripts/codegen-witness.sh` is the existing tool that reads +assembly; adding a "no `vpshufb` in the masked dot on AVX2" assertion +there would make the rule cover what LLVM emits too. + +**N13 — W4 is half done, by the right route.** lance-graph's W4 asks ndarray +for `dot4_u8_i8_add` + nibble ops "ONLY if needed by a proven consumer" +(`:564`). #386 added exactly those, triggered by turbovec's raw `vpdpbusd` +and `SDOT` kernels. The remaining half is turbovec moving onto the facade, +which is turbovec's repository (not in this session's scope) and lance-graph +W5 (the bridge is "likely non-building", `:57-61`). + +**N14 — the U8→I8 bit view and R3 are one ruling.** U6 asks for a +`U8x64 → I8x64` bit view (today a store/load, 1.16–1.27 ns per block). R3 +forbids mixing u8 and i8, and the 2 existing R3 hits are the same kind of +crossing (u8 `splat` cast to an `i8` intrinsic parameter). A ruling that +permits a named, zero-cost reinterpret — and still forbids arithmetic on +the reinterpreted value — would settle both. + +**N15 — CI does not run the new lib tests on NEON or wasm.** #386's own note: +"CI never builds aarch64/wasm lib tests, only the two parity programs". The +`u16x32_facade_parity` and `dot4_nibble_facade_parity` modules run on x86 CI +only; NEON and wasm are covered through codes 0x500–0x507 in the parity +programs. The head-to-head tests also ran on the other tiers locally only +(`turbovec9-u9-head-to-head.md:135-136`). + +### R2.4 Fruit, revised + +Closed this pass: U1 (#393); W4's ndarray half (#386); the `U16x32` part of +H1 (#385). + +Excluded, do not reopen without new evidence (from #392): bit-plane +multiplication as a B replacement; lazy EMPTY/FULL flags in bit-sliced +counters. + +| # | item | size | why first | +|---|---|---|---| +| L6 | add the AVX2 "no `vpshufb` in masked dot" check to the codegen witness, initially as an expected-fail record | low | N12; makes the hidden shuffles visible | +| L7 | U2 probe: AVX2 streamed B, bandwidth vs the 4 `vpshufb` | low | the number is unexplained | +| L3, L4, L1, L2, L5 | as in §5 | low | unchanged | +| H9 | AVX2 `mask_blend` without the per-lane loop (U5), then U4 (sign plane → ±1 bytes) on top of it | high | biggest measured AVX2 cost (75–81 vs 10 ns/64); R2 burn-down item #1 | +| H1' | parity for the remaining 15 facade types, `U16x32` as the template | high | #385 shows it finds real bugs | +| H3' | AVX2 popcount: measure scalar-`popcnt` (vectorization blocked) against the nibble table; exclusion with the number if the table wins | high | N8 reframed | +| H5, H6, H2, H4, H7, H8 | as in §5 | high | unchanged; H5 still first for data safety | +| V9 | U7 / W1 / W6: recall for stored SM9 vs Lloyd-Max IDs on a real index | heavy | N11; TurboVec owner | +| V10 | the N14 ruling (named u8→i8 reinterpret) | heavy | operator | +| V11 | fused single-widen SM9 op for non-VNNI tiers; AVX-VNNI 256-bit and NEON `USDOT` arms; signed i8×i8 facade word | heavy | recorded loose ends of #386/#387, each needs a consumer | +| V1–V8 | as in §5 | heavy | unchanged | + +### R2.5 Harvest ledger, this pass + +| card | task | result | spot check | +|---|---|---|---| +| substrate-mapper | five U9 knowledge docs + board diff | DONE, all read | `u9-e5-probe.md:16-24`, blackboard U1–U7 rows re-read | +| substrate-mapper | `src/` diff (2,433 lines) + parity diff | DONE, 83 added `pub fn` | AVX2 `sum8_u8_add_masked` body re-read: `for h in 0..2` is the two-half split, not a per-lane loop | +| substrate-mapper | three lance-graph docs | DONE, all read | entry `:28-36`, `:555-572` re-read | +| census-counter | 13 counts | DONE | R2 = 157 re-run; `cpu_guard` path miss was my brief's error | + +Misses: one, mine (wrong path in a brief). No card concluded or miscounted. + ## 0. What the six ndarray PRs did | PR | closed | opened or left |