Skip to content

Commit 63d6d52

Browse files
opt
1 parent d8dedf0 commit 63d6d52

2 files changed

Lines changed: 254 additions & 230 deletions

File tree

notes/dip_phase_packet_design.md

Lines changed: 68 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,71 @@ cells only, boundary 0, DRAM flat at ~3 passes.
146146
5. **Small-N (N ≤ leaf) stays on the current in-cache vectorized stages** — it
147147
is already fine there and needs no blocking.
148148

149-
## Status
150-
151-
Corrected: the transpose-four-step is retired (fails transport conservation +
152-
introduces complex arithmetic Bruun does not need). The real diagonal walk
153-
`phase_fft_real` is the target — exact r2c, real cells only, boundary transport
154-
0. The remaining build is the DEPTH-FIRST CACHE-BLOCKING of that walk (interior
155-
log N → ~2 passes) with natural order preserved at both ends; the vectorized
156-
real cells and the ordering theorem are in hand. `experiments/dip_phase_packet.py`
157-
(the four-step prototype) is kept only as the retired reference.
149+
## SHIPPED (2026-07-05): the in-place diagonal span walk
150+
151+
The C++ that landed in `src/detail/bruun_dip_kernel.hpp` supersedes both the
152+
Python four-step prototype AND the self-sorting variant above. The realization
153+
that unlocked it: writing each cell's two children INTO THE PARENT'S OWN SPAN
154+
makes every span self-contained forever — fully in-place (work = n, down from
155+
2n; no ping-pong, no scratch, concurrency-safe), depth-first (cache transition
156+
emergent, no block knobs), the Nyquist-promotion copy free, and the SIMD purely
157+
vertical (4/8 contiguous streams, broadcast twiddles, zero permutes; radix-4
158+
fused two-level cells above the boundary granularity, `norm2_fused`-shape).
159+
160+
The boundary (the user's dual-polarity insight, validated hard): pay the
161+
long-range disorder as COMB STREAMS at both ends, confine all scramble to L1.
162+
The leaves of any subtree `(d, E)` are the bins `{jE ± d}` (two combs), and
163+
the tree-order → bin-order permutation within a subtree is UNIVERSAL
164+
(d-independent; leaf value = `m·E + s·d` with `(m,s)` path-only), so one tiny
165+
shared table `T_w` per span size drives a streaming per-subtree egress
166+
(forward) / ingress (inverse). Asymmetries that measurement forced:
167+
- inverse ingress = comb reads + L1 scatter (a separate injection pass and a
168+
fused per-leaf gather both lose);
169+
- forward egress = L1 gather + comb writes, with a radix-2 parity step so the
170+
RFO-paying writes land on the densest combs the L1 span budget allows (the
171+
inverse skips the parity step — reads prefetch at any density);
172+
- seed at level 8, not 16: 16+16 power-of-two-strided streams alias L1 sets
173+
past associativity (measured 9× the 8-stream seed at 1M).
174+
175+
Measured vs DIF (M-series NEON, min-of-5, `dif_vs_dip_benchmark`):
176+
177+
| n | fwd DIP/DIF | inv DIP/DIF |
178+
|---|---|---|
179+
| 1024 | 1.28 | 1.00 |
180+
| 4096 | ~1.1–1.5 | **0.57–0.89 (wins)** |
181+
| 65536 | 1.56 | 1.10 |
182+
| 262144 | ~2.3 | 1.11 |
183+
| 1048576 | 1.89 | 1.17 |
184+
| 4194304 | 2.22 | 1.65 |
185+
186+
Starting point was fwd 2.6×/inv 1.6–2.4× (breadth-first ping-pong). The
187+
inverse is at DIF parity (or wins) through 1M.
188+
189+
### Forward optimization pass (2026-07-05)
190+
191+
Two wins landed, both grounded by isolation profiling (`headcmp`/`wcmp`
192+
harnesses, in-process back-to-back to cancel this machine's severe thermal
193+
noise — DIF-ratio benchmarks swung ±30% run-to-run and could not be trusted):
194+
- **`phase_table_index` divide → shift.** `d*n/(2e)` was a 64-bit `sdiv` at
195+
every node; `2e` is always a power of two and exactly divides `d*n`, so it is
196+
`(d*n) >> (ctz(e)+1)`. (Index compute was NOT the dominant cost — `noidx`
197+
`nocells` — but the sdiv still cost real cycles at small/mid n.)
198+
- **Inline the `span8` leaf codelets** (`BRUUN_ALWAYS_INLINE`). They were real
199+
calls; inlining removed the leaf-call overhead. Net: 4096 forward 1.5→1.11×.
200+
201+
What profiling RULED OUT (don't retry):
202+
- **Egress is cheap** — isolating it (`wcmp`) shows 0–11% of the forward,
203+
mostly noise; it is NOT the gap. A split-comb (two monotonic streams) egress
204+
measured a dead wash back-to-back.
205+
- **The WALK is the whole forward cost**, and the forward walk is ~35% slower
206+
than the inverse walk at 64K (both cache-resident, same cell count) yet
207+
converges by 4M — i.e. a COMPUTE/scheduling asymmetry in the forward cell's
208+
dependency chain (`cell4_fwd_ip`), not transport. It hides once DRAM-bound.
209+
- The forward radix-2 parity step at `2*kEgressW` is net-neutral (its
210+
egress-density rationale is void now that egress is cheap); kept only because
211+
removing it is within noise and changes span sizing.
212+
213+
Remaining forward levers (larger efforts, uncertain payoff on this noisy box):
214+
flatten the recursion into a DIF-style precomputed op schedule (kills the
215+
residual call overhead), or restructure `cell4_fwd_ip` to shorten its two-level
216+
critical path (the 64K compute asymmetry). The inverse needs nothing.

0 commit comments

Comments
 (0)