Skip to content

Commit 854924a

Browse files
authored
Merge pull request #307: gated *_to_mask_under predicates + Morton lattice shift + hex tenant probe
Hex-tenant probe (D-GTM-0m), mask_shift_morton (D-GTM-1m), and the gated *_to_mask_under family (D-MRX-0)
2 parents 37e25da + 3a124bb commit 854924a

14 files changed

Lines changed: 2674 additions & 11 deletions

File tree

‎.claude/blackboard.md‎

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,158 @@
1+
## 2026-09-14 (5) — STORNO on entries (3) and (2), from the #307 council (measurement-skeptic, kernel-membrane, overclaim): same numbers, tighter words
2+
3+
The numbers stand; the wording ran past them. Corrections, each to the sentence it replaces:
4+
5+
- (3) *"full-field −14 %"* → **−5 % to −14 % across two runs** (14 517 vs 16 944 and 16 507 vs 17 462): within this density's cross-run noise, i.e. no reliable full-field win over the per-bit loop. The table pinned the better run.
6+
- (3) *"node-span −66 % (n 17.0 → 5.7 µs)"* → **on this fixture (frontier confined to one 64-word level-1 tile, 1/16 of the field, 29 % of the tile span), restricting the shifts to the tile span cuts the x=0 STEP from 16.9 to 5.7 µs (−66 %, one run).** It is a step ratio, not an op ratio: every arm still carries the full-field housekeeping (scratch zeroing, `OR2_AND`, the reverse walk over 1024 words) AND the probe's own per-rep reset — the `rails_run` copy is **786 KiB** (65 536 × 12 B), not the "8 KiB" the probe comment said (fixed), ~1 µs per step amortized inside every `n`. A frontier straddling k nodes pays Σ spans.
7+
- (3) *"the ratio survives the degree-1 ablation"* → **a −33 % gain survives** (4 487 vs 6 704); the ratio halves. What the ablation shows is that the word op's advantage is not hex-specific; the other half of the degree-6 gain scales with the per-bit loop's direction count.
8+
- (3) *"remaining 5.7 µs is the field-wide housekeeping"* → housekeeping **plus** the reset amortization; neither is separated.
9+
- (3) *"delta-frontier on top of the node span buys nothing (5.7 → 6.0)"* → no gain; the +0.4 µs is inside single-window noise, not a finding.
10+
- (2)/(3) *"coal = 8.9 µs = 0.48 maintained steps"* → **8.8–13.7 µs across three runs = 0.47–0.76 steps at x=4** (single window each). *"ternlogq 281 ns/pass"* → 280–300 across runs, max residual ≤ 2.8 %; the "1.7 % at x=1" is fit-derived. *"228–462×"* range-reveal ratio → ~200–490× across three runs at N = 65 536; the range arm's floor is the 8 KiB output clear.
11+
- (3) *"68/68 gates"* → 68/68 probe ROWS pass the three spread gates on one fixture and one seed. *"1.8 % density"* → 1.8 % of the field, **29 % of the tile span** — the denominator that explains why the word op loses full-field and wins node-span.
12+
- Method: one 50 ms window per cell, no repeats, no variance reported; cross-run spread is visible only because three runs were banked (ladder x=0 spread 3 %, `shift` 14 %, coal 56 %). Single-fixture observations, all of them.
13+
- Kernel-membrane, on `mask_shift_morton`: the slice IS the field — a sub-span shift is NOT the restriction of the full-field shift (no carry crosses the span edge). The probe's node arm was correct only because a level-1 tile is Morton-aligned and the source is confined to it; that law is now on the op's doc and the probe asserts `lo % len == 0` at the call site. The op is a lattice axis shift (four Cartesian moves; hex diagonals are two-call compositions), renamed as such in its docs.
14+
- Codex P2 on the probe: the Hebbian reverse walk credited any predecessor in the accumulated `state`; under the delta-frontier arms only `delta` cells propagated, so an older active neighbour of a newly reached cell could be credited for a firing it did not carry. Fixed (attribution against the step's SOURCE frontier, `delta` published after the walk) and **measured inert on this fixture — all 68 rows' fired/step and survivors identical before and after** — because eligibility is static here: an older predecessor's target was already settled when that predecessor was itself in the delta. Real for dynamic eligibility, unobservable in this probe.
15+
16+
## 2026-09-14 (4) — D-MRX-0: `*_to_mask_under` — the gated predicate mask-risc's `Pred { under }` promised and T1 lacked
17+
18+
**Why now.** lance-graph #1225's kernel-membrane review (PR2 council) ruled the
19+
executor may not compose a gated predicate from `*_to_mask` + `mask_and` (the
20+
whole plane, twice) nor skip words itself (a compute path above the facade):
21+
*"PR3 lands `*_to_mask_under` first."* This is that word.
22+
23+
**Shape.** ONE private engine, `pack_under::<T, L>(name, values, under, out,
24+
group_bits)`: `out[w] = under[w] & pred(values)[w]`, the predicate evaluated
25+
only where `under[w] != 0`. The skip is word-granular (64 rows) — the unit
26+
the caller holds; a per-group skip is reachable and deliberately not done. An
27+
executor may skip coarser (mask-risc speaks of 1024-row chunks) with a result
28+
identical by construction (a skipped chunk is an all-zero gate) — not
29+
exercised by any test. COMPARE cost ∝ live gate WORDS (a sparse frontier
30+
spread across every word pays every compare); the per-word gate test and
31+
zero store stay ∝ rows/64; no timing of the skip exists, only the call count
32+
below. The predicate's own tail law makes a phantom gate bit past `n` vanish —
33+
the AND conforms regardless, so a gate never needs cleaning before use
34+
(surplus `under` words are never read). Ten public
35+
members, one per `Pred` variant (`gt/lt/ge/le/eq/ne_i32`, `eq/ne_u32`,
36+
`ternary_match_u32/u64`), each one `pack_under` call over the SAME lane op its
37+
ungated sibling uses — no new backend semantics, so no backend file changed.
38+
39+
**Falsifiers.** (1) family ≡ `ungated & gate` word-for-word at ten lengths with
40+
phantom gate bits present; (2) the skip is MEASURED: a counting closure sees
41+
20 of 40 groups under an alternating gate, 40 under all-ones — disable-run
42+
red (`left: 40, right: 20`) with the `gate == 0` early-out removed, green
43+
restored (verified once, not re-runnable); (3) phantom bits do not leak at
44+
n = 70; (4) short gate panics; (5) each public member is ONE `pack_under`
45+
delegation, counted from the source, so the skip test on the private engine
46+
covers all ten;
47+
doctests 10/10. Parity harness group 10 (`0xAxx`, `check_predicates_under`):
48+
alternating-zero gate with random phantom bits, reference reads the gate BIT
49+
per row. Native (AVX2), nightly (`core::simd`), wasm simd128 and wasm scalar: all
50+
10/10 bit-identical here; neon has no qemu in this sandbox, and the
51+
`neon-simd/parity-qemu` CI job on #307 is the witness for that arm (green).
52+
53+
**Allocation.** Engine + first member by the orchestrator; nine members +
54+
tests by a worker agent against a written spec (its Bash died on a full disk
55+
mid-run — the tasks tmpfs and the checkout share one allowance — so it landed
56+
Tasks A–C unverified and reported exactly that; the orchestrator gated
57+
centrally after freeing 6.5 GB of stale scratch targets and landed the parity
58+
arm itself). W1a deviation record as for `mask_andnot` / `mask_ternlog`:
59+
free-fn family shape beside its siblings, not a struct method.
60+
61+
**Loose ends.** The strided operand family (12-in-16-byte register compares
62+
for `LaneRef`) and `u8/u16/u64` compare-to-mask (DuckDB matrix G1/G2) remain
63+
T1 gaps; neither is needed for PR3's first executor.
64+
65+
## 2026-09-14 (3) — D-GTM-1m MEASURED: `mask_shift_morton` lands; the win is in the NODE SPAN, not the op — n = 17.0 → 5.7 µs (−66 %)
66+
67+
`mask_shift_morton` (255c36d) is bit-exact (F1–F4, parity 9/9 on native/
68+
nightly/wasm/wasm-scalar; neon-qemu absent here) — and over the FULL field it
69+
barely moves `n`: 14.5–16.5 µs vs 17.0 µs for the per-bit loop. The worker
70+
implementer named the mechanism correctly: the op is FIELD-size-bound (1024
71+
words × ~8 passes per direction) while the per-bit loop is ACTIVE-bound, so at
72+
1.8 % density they cost about the same and the op loses once gates thin the
73+
frontier.
74+
75+
**The fix is the fixed-spatial-distribution dividend a second time.** A trie
76+
node is a contiguous word span AND a square Morton sub-field (the level-1 tile =
77+
64 words = a 64×64 field, `log2(64)` even), so the shifts run over the node's
78+
own span with no correctness change (the source ⊆ tile, so no carry enters the
79+
span; a carry leaving it is what `& tile` removes anyway). Measured, dirs = 6,
80+
x = 0, same gates green (68/68 rows, 0 heap B/step):
81+
82+
| arm | ns/step | vs ladder |
83+
|---|---|---|
84+
| ladder (per-bit, full state) | 16,944 | — |
85+
| nnue (per-bit, delta frontier) | 9,296 | −45 % |
86+
| shift (word op, full field) | 14,517 | −14 % |
87+
| **node (word op, tile span)** | **5,677** | **−66 %** |
88+
| node+g / node+nn / node+nn+g | 5,722 / 6,044 / 5,774 | — |
89+
90+
Delta-frontier on top of the node span buys nothing (5.7 → 6.0), as predicted:
91+
a span-bound op does not care how many bits are set. Degree-1 control: node
92+
4.5 µs vs ladder 6.7 µs — the ratio survives the E-Q8 ablation, so the gain is
93+
the word op, not the six. The ternlog fit is unchanged (281 ns/pass, n = 17.0 µs
94+
on the ladder arm by construction). Remaining 5.7 µs is the field-wide
95+
housekeeping (scratch zeroing, `OR2_AND`, the Hebbian reverse walk over 1024
96+
words) — the next rung restricts THOSE to the node span too and re-measures;
97+
not claimed here.
98+
99+
Rule extracted: **a word-level op pays for the span it is given; give it the
100+
node, never the field.** The same statement as "top-down is a range, not a
101+
compare" (2026-09-14 (2)), now on the grey side.
102+
103+
## 2026-09-14 (2) — D-GTM-0m: the hex TENANT — top-down traversal AND spread on ONE Morton-keyed SoA; `step = x·ternlogq + n` measured, and the chain is 1.7 % of it
104+
105+
Probe: `examples/hex_tenant_mq_probe.rs` (`--release`, committed; output banked
106+
in the session scratchpad). Operator statement it builds (2026-09-14): *"static
107+
traversal top down AND plasticity (spread) in the same substrate — SoA gets a
108+
hex tenant with 6×2×8 bit and the field is a trie (fixed spatial distribution)."*
109+
This is `gemm-ternlog-mask-consolidation-v1.md` §9 M1/M1b/M2/M3 and §11.10
110+
(`substrate == mask geometry == projection surface`) executed on the merged #306
111+
facade, not argued. Substrate: 65,536 rows = 256×256 axial hex cells, **row =
112+
Morton(q, r)**, payload = the V3 12-byte register read `6×(u8:u8)` with rail `d`
113+
= hex direction `d`, `u8:u8 = (permeability, strength)`. §9 R1 ("not obviously
114+
the same six") is resolved by construction: adjacency and carving ARE the same
115+
six once the rail index is the direction. Three gates, all green (32/32 cells):
116+
range reveal == TCAM reveal at every level/prefix; Morton-arm spread == an
117+
independent row-major axial BFS at every step (plasticity bytes compared too);
118+
hot-path heap = **0 B/step** everywhere (counting allocator).
119+
120+
**White — top-down is a RANGE, not a compare.** A trie node at nibble level L is
121+
`2^(16−4L)` CONTIGUOUS rows, so revealing it is a range write: **49–99 ns** vs
122+
**22.4–22.8 µs** for the general `ternary_match_u32_to_mask` sweep over the
123+
address column — **228–462×**. That is the fixed-spatial-distribution dividend
124+
stated as a number: the TCAM op stays for addresses that are NOT laid out (the
125+
D-GTM-0l linker case); a minted, Morton-keyed tenant never pays it.
126+
127+
**Grey — the cost model, fitted (identity gates, survivors held at 1175):**
128+
`step = x·ternlogq + n` with **ternlogq = 291 ns/pass** (8 KiB masks, 0.285
129+
ns/word) and **n = 17.3 µs**, max residual 2.8 % over x ∈ {0,1,2,4,8,16,32}.
130+
So at x = 1 the chain is **1.7 %** of the step; even x = 32 only doubles it.
131+
`n` is the ONE non-mask op on the path — the per-active-bit hex shift (dilated-
132+
integer add per direction). **That is the missing substrate word**: a mask-level
133+
neighbour shift on the Morton lattice (`mask_shift_hex(state, d, dst)` — within
134+
a nibble a 4×4 block shift, carries across blocks), which would fold `n` into a
135+
handful of word passes. Filed, not built. First rung without it: the **NNUE
136+
reading** — spread from the DELTA frontier (`scratch & !state`), never from the
137+
accumulated state — gives the identical closure (gate green) at **8.8 µs**
138+
(−48 %), 8.4 µs with the real gates. The E-Q8 control is in the table (degree-1
139+
arm: 7.0 µs, 301 survivors — a different closure, so a cost floor, not a cost
140+
equivalence).
141+
142+
**Coal.** One re-chain (regenerate a resident mask from its column,
143+
`gt_i32_to_mask` over 256 KiB) = **8.9 µs = 30.6 ternlogq passes = 0.48
144+
maintained steps** at x = 4. M2 is linear: x → x±1 is one pass (291 ns), no
145+
cliff. M1b generation (6 eligibility masks) = 75 µs once per mask generation.
146+
Plasticity: 740 rows' strength bytes bumped IN the register (1,263 firings over
147+
24 steps), on survivors only — the first version fired on every re-reached cell
148+
the gates then removed, which is the "fire before you know it survived" bug the
149+
reverse walk fixes.
150+
151+
**Stated limits.** The T1 compare is i32-wide, so the u8 permeability column is
152+
widened 4× for `gt_i32_to_mask` — `n_gen` and coal are UPPER bounds; a u8/u16
153+
compare-to-mask is a T1 addition. One fixture density (62 % permeable), one
154+
tile size, timing floor 50 ms, no `perf`. No production caller; this is W0.
155+
1156
## 2026-09-14 — AVX2 arm of the mask family MEASURED, not rewritten: 6 of 10 shapes were already packed, 4 earned intrinsic realizations
2157

3158
**The pre-compaction plan was wrong, and the instrument said so before code

‎.claude/knowledge/vertical-simd-consumer-contract.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,3 +513,35 @@ simd_{avx512,avx2,neon,wasm,scalar}.rs peer backends, each owns realization
513513
because it is spelled as a loop; it is scalar when `--emit asm` on the
514514
shipped method says so — and "mostly packed" is a category the oracle
515515
must be able to report, because a peel is invisible to any parity test.
516+
- **A gated predicate is a facade word, not an executor trick (2026-09-14,
517+
`*_to_mask_under`).** mask-risc's `Pred { under }` promises "evaluate only
518+
where the gate has a survivor"; the kernel-membrane review of lance-graph
519+
#1225 ruled that T1 must carry that word before any executor composes it,
520+
because an executor that spells it as `*_to_mask` + `mask_and` pays the
521+
whole plane twice and an executor that skips words itself has grown a
522+
compute path above the facade. The family (`gt/lt/ge/le/eq/ne_i32`,
523+
`eq/ne_u32`, `ternary_match_u32/u64` — the ten `Pred` variants, one each)
524+
shares ONE private engine (`pack_under`) whose skip is at word
525+
granularity: `under[w] == 0` writes zero and evaluates nothing. Compare
526+
cost ∝ live gate WORDS (a sparse frontier spread across every word pays
527+
every compare); the per-word gate test stays ∝ rows/64. Result ≡
528+
`pred & under` bit-for-bit, and the predicate's own tail law makes a
529+
phantom gate bit past `n` vanish without a clean (surplus `under` words
530+
are never read). The can-it-fire test counts the closure invocations
531+
(5 live words × 4 groups = 20 of 40), so "skips" is a measured call
532+
count — no timing of the skip exists.
533+
- **`mask_shift_morton` is admitted on a narrower argument than the rest of
534+
this module, and the argument is written down (2026-09-14, kernel-membrane
535+
review of ndarray #307).** The admission test every word here passes: a
536+
slice-level composition of lane ops with no ISA branch (`U64x8` shifts,
537+
ANDs, ORs — zero `core::arch`, `#![forbid(unsafe_code)]` holds). What no
538+
other word here does: it asserts a caller-held ADDRESS MODEL (word index =
539+
Morton key of an 8×8-block grid; `q` on even bits, `r` on odd) and treats
540+
the slice AS the field, so a sub-span shift is not the restriction of the
541+
full-field shift. That is the cost of admitting it, and the fence is the
542+
op's own doc paragraph ("The slice IS the field") plus the caller's
543+
alignment assert. It stays here rather than in a consumer because the
544+
alternative — the per-active-bit loop the hex probe measured at 17.0 µs a
545+
step — is exactly the above-facade compute path T1 exists to prevent; and
546+
it is named a lattice axis shift, not a hex op, because the four variants
547+
are Cartesian and every hex diagonal is a two-call composition.

0 commit comments

Comments
 (0)