Skip to content

perf(evolution): ⚡ carry cross-rank queries as a width-adaptive record - #263

Open
diagonal-hamiltonian wants to merge 1 commit into
perf/epoch-stamp-noshrink-v2from
pr/query-wire-v3
Open

perf(evolution): ⚡ carry cross-rank queries as a width-adaptive record#263
diagonal-hamiltonian wants to merge 1 commit into
perf/epoch-stamp-noshrink-v2from
pr/query-wire-v3

Conversation

@diagonal-hamiltonian

@diagonal-hamiltonian diagonal-hamiltonian commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Carry cross-rank queries as a width-adaptive record

pr/query-wire-v3 249aee8, on PR 7 (#259) 8dbe2a5. One commit, 18 files, +2569/−285.

The layer-build query path carried one dense word per 64 modes per query, at a fixed stride. This replaces it with a per-term-minimal record and stops the two paths that never needed the wire from using it.

What changes

  1. The structural cutoff is decided from an inline (k, d) digest instead of the wider comparison it replaces. This also removes the monoprop_DIGEST_CUTOFF knob.
  2. Cross-rank queries travel as a width-adaptive record — the fixed stride the old CompactQuery design assumed is gone.
  3. A rank resolves its own queries from positions, not through the cross-rank path.
  4. Sink::kStride becomes incoming_layout() / querier_layout(), fixing a latent inconsistency: ContractSink::kStride was the fused width while on_response_block got the plain buffer and used the plain-stride default — correct only by accident. A wrong layout reads a neighbouring record's phase, which is a coefficient sign flip, not a crash.
  5. bulk_insert gains a group-prefetched hashed path, shared by the cross-rank and self inserts.

They ship together because 2 and 3 share the OperatorIndex position store, and 1 is not measurable without them.

Timings

Arms: main 48cadcb vs this branch, both ENABLE_PROFILE=OFF. Stacked, so this is PR 7 + PR 5 — PR 7's own timing was a null in both its campaigns (0 and 1 of 24, all inside 0.98–1.01x), and memory includes its 2 B/term.

port/main, so every row below 1.00 is this branch faster. 10 interleaved reps per cell in one allocation, order flipped per (rep, cell); ratios divided per rep then median, never a ratio of medians. rung is lower_atol (with Trotter steps where they differ) and separates cells that are different model sizes. No multiplicity correction — one branch against main is not a family — so every p is a raw two-sided sign test.

Single thread (1 rank x 1 partition)

operation N layout rung main (ms) port (ms) port/main agree p
build_graph[hubbard] 1 X_1x1 1e-3/2st 39655.5 35700.2 0.899 (1.11x faster) 10/10 0.00195
propagate[hubbard] 1 X_1x1 3e-5 49173.8 59668.8 1.214 (1.21x slower) 10/10 0.00195
build_graph[pauli] 1 X_1x1 1e-4 513.8 735.6 1.433 (1.43x slower) 10/10 0.00195
energy[pauli] 1 X_1x1 1e-4 85.2 84.4 0.991 8/10 0.109
gradient[pauli] 1 X_1x1 1e-4 179.8 180.2 1.003 7/10 0.344
propagate[pauli] 1 X_1x1 1e-4 413.9 636.6 1.542 (1.54x slower) 10/10 0.00195

Layouts A (1x128) and B (8x16)

operation N layout rung main (ms) port (ms) port/main agree p
build_graph[hubbard] 1 A_1x128 1e-04/2st 3959.8 3996.4 1.005 7/10 0.344
propagate[hubbard] 1 A_1x128 1.25e-05 17778.6 15753.7 0.886 (1.13x faster) 10/10 0.00195
build_graph[pauli] 1 A_1x128 5e-05 13996.7 14024.9 1.002 6/10 0.754
energy[pauli] 1 A_1x128 5e-05 2425.7 2417.1 1.001 6/10 0.754
gradient[pauli] 1 A_1x128 5e-05 11282.4 11167.6 0.990 9/10 0.0215
propagate[pauli] 1 A_1x128 5e-05 11895.9 11055.1 0.936 7/10 0.344
build_graph[hubbard] 2 A_1x128 1e-04/2st 8308.9 6799.1 0.818 (1.22x faster) 9/10 0.0215
propagate[hubbard] 2 A_1x128 1.25e-05 46778.1 40840.0 0.868 (1.15x faster) 10/10 0.00195
build_graph[pauli] 2 A_1x128 5e-05 28993.5 27249.6 0.938 10/10 0.00195
energy[pauli] 2 A_1x128 5e-05 4730.4 4671.1 1.001 5/10 1
gradient[pauli] 2 A_1x128 5e-05 16352.9 15913.8 0.971 8/10 0.109
propagate[pauli] 2 A_1x128 5e-05 23606.7 22084.8 0.941 8/10 0.109
build_graph[hubbard] 1 B_8x16 1e-04/2st 2844.3 1999.9 0.702 (1.42x faster) 10/10 0.00195
propagate[hubbard] 1 B_8x16 1.25e-05 19220.8 16460.4 0.856 (1.17x faster) 10/10 0.00195
build_graph[pauli] 1 B_8x16 5e-05 13308.6 12633.6 0.949 9/10 0.0215
energy[pauli] 1 B_8x16 5e-05 2849.8 2847.3 0.997 6/10 0.754
gradient[pauli] 1 B_8x16 5e-05 11566.7 11552.0 0.998 6/10 0.754
propagate[pauli] 1 B_8x16 5e-05 10537.7 9651.1 0.917 9/10 0.0215
build_graph[hubbard] 2 B_8x16 1e-04/2st 2937.2 1838.2 0.631 (1.59x faster) 10/10 0.00195
propagate[hubbard] 2 B_8x16 1.25e-05 13811.8 12176.7 0.880 (1.14x faster) 9/10 0.0215
build_graph[pauli] 2 B_8x16 5e-05 9405.4 8967.4 0.947 9/10 0.0215
energy[pauli] 2 B_8x16 5e-05 759.8 773.9 1.018 8/10 0.109
gradient[pauli] 2 B_8x16 5e-05 4656.9 4625.9 0.992 9/10 0.0215
propagate[pauli] 2 B_8x16 5e-05 7311.5 6823.2 0.935 10/10 0.00195

Peak RSS (node sum)

cell N layout rung main (GiB) port (GiB) port/main agree p
hubbard-fresh 1 A_1x128 1.25e-05 9.53 9.35 0.981 10/10 0.00195
hubbard-fresh 1 A_1x128 1e-04/2st 26.61 24.24 0.911 10/10 0.00195
pauli-fresh 1 A_1x128 5e-05 10.40 10.33 0.994 10/10 0.00195
pauli-graph 1 A_1x128 5e-05 18.13 17.89 0.987 10/10 0.00195
hubbard-fresh 2 A_1x128 1.25e-05 10.34 10.04 0.972 10/10 0.00195
hubbard-fresh 2 A_1x128 1e-04/2st 31.21 29.10 0.932 10/10 0.00195
pauli-fresh 2 A_1x128 5e-05 10.90 10.74 0.986 10/10 0.00195
pauli-graph 2 A_1x128 5e-05 36.71 36.56 0.996 10/10 0.00195
hubbard-fresh 1 B_8x16 1.25e-05 11.05 10.75 0.972 10/10 0.00195
hubbard-fresh 1 B_8x16 1e-04/2st 28.40 25.72 0.905 10/10 0.00195
pauli-fresh 1 B_8x16 5e-05 12.16 11.92 0.979 10/10 0.00195
pauli-graph 1 B_8x16 5e-05 19.68 19.46 0.989 10/10 0.00195
hubbard-fresh 2 B_8x16 1.25e-05 14.16 13.85 0.977 10/10 0.00195
hubbard-fresh 2 B_8x16 1e-04/2st 34.80 32.64 0.939 10/10 0.00195
pauli-fresh 2 B_8x16 5e-05 14.98 14.71 0.979 10/10 0.00195
pauli-graph 2 B_8x16 5e-05 40.77 40.55 0.995 10/10 0.00195
hubbard-fresh 1 X_1x1 1e-3/2st 12.10 11.71 0.970 10/10 0.00195
hubbard-fresh 1 X_1x1 3e-5 1.34 1.31 0.971 10/10 0.00195
pauli-fresh 1 X_1x1 1e-4 0.29 0.29 0.995 9/10 0.0215
pauli-graph 1 X_1x1 1e-4 0.30 0.30 0.996 9/10 0.0215

What the grid shows, and where it does not

Nothing regresses at any production layout. Across P = 128 and P = 256, both models, all four operations, every resolved cell is a win (0.63–0.99x) and the rest are flat.

build_graph needs more than one rank — and it is ranks, not partitions, since layouts A and B share the same flat world P. Both models are flat at 1 rank (1.005 7/10, 1.002 6/10) and win from 2 up, but the magnitude diverges: hubbard grows monotonically (1.22x / 1.42x / 1.59x at 2 / 8 / 16), pauli jumps once and saturates (0.938 / 0.949 / 0.947).

propagate wins a flat 1.13–1.17x at every rank count from 1 to 16, all four cells resolved, so that share is not the exchange.

energy and gradient are a null control inside the same cells. Neither touches the query wire; across the nine cells running them they land in 0.971–1.018, same binary, same allocation, same reps as the rows that moved.

The scope limit: the record's fixed per-query cost needs something to amortise against, and at one partition there is nothing. Three of the four single-thread operations regress 1.21–1.54x, 10 of 10 reps each. This is not a narrow-system effect — pauli is the narrow model and it wins 0.92–0.95x once the problem is large (91.3M terms), so the axis is amortisation, not width. The one P = 1 gain, build_graph[hubbard] at 1.11x, is the digest becoming visible where emit is ~67% of the layer instead of ~12%.

The digest itself cannot be confirmed at 128 partitions, where it is ~0.8% of build_graph wall — below its paired noise floor. Its independent evidence is retired instructions: 0.9128x around build_graph, 4 of 4 reps, −23.4 per emitted term — a different instrument on an earlier form of this branch, not this campaign.

Bit-identity

Determinism holds only at fixed (ranks, partitions) — across partition counts the contract is tolerance-equality — so the geometry is part of the claim. 7 of 7 cells bit-identical at (R × S) in {1x1, 2x1, 2x8, 4x8} across build_graph, propagate and pauli, job 1825793, compared per rank, unmerged, byte for byte. Witnessed on the pre-re-cut form of these commits; it cannot be re-run on this base, because the gate needs each arm to supply benches/_builders.py from its own checkout and #227 moved those modules into a separate package.

Gates and reproducibility

ctest -L unit 249/249, -L serial 248/248, and the Python MPI suite 592 passed at each of 1x1, 1x16, 2x8 and 8x16. strings -a finds no LAYERPROF/COMMPROF, confirming no profiling instrument is linked, and finds matched_scratch_bytes, confirming PR 7 is in the base.

Not reproducible from this diff. Deucalion, 2x AMD EPYC 7742 / 128 cores / SMT off / NPS4 / 242 GiB per node; a private harness that never ships; two prebuilt venvs, extension md5s 1250a27e (main) and b4e76812 (this branch).

@github-actions

Copy link
Copy Markdown

Docs preview: https://pr-263.monoprop-docs.pages.dev

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (perf/epoch-stamp-noshrink-v2@8dbe2a5). Learn more about missing BASE report.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@                       Coverage Diff                       @@
##             perf/epoch-stamp-noshrink-v2     #263   +/-   ##
===============================================================
  Coverage                                ?   97.70%           
===============================================================
  Files                                   ?       14           
  Lines                                   ?      742           
  Branches                                ?       98           
===============================================================
  Hits                                    ?      725           
  Misses                                  ?       12           
  Partials                                ?        5           
Flag Coverage Δ
cpp 97.70% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

@diagonal-hamiltonian
diagonal-hamiltonian marked this pull request as draft August 21, 2026 07:17
@diagonal-hamiltonian
diagonal-hamiltonian marked this pull request as ready for review August 21, 2026 08:13
@github-actions github-actions Bot added documentation Improvements or additions to documentation python labels Aug 21, 2026
Five changes to the layer-build query path, squashed because the middle three
share one data structure and the first is not measurable without them.

The structural cutoff is decided from a (k, d) digest carried inline rather than
from the wider comparison it replaced. Cross-rank queries then travel as a
width-adaptive record, dropping the fixed stride the older CompactQuery design
assumed. Queries whose owner is this rank resolve from positions instead of
going through the cross-rank path at all.

Two further changes ride with them and are named here because a reviewer cannot
revert along a mechanism the message does not mention. Sink::kStride becomes
incoming_layout()/querier_layout(): ContractSink's stride was the fused width
while on_response_block was called with the plain buffer and took query_phase's
plain-stride default, so the two agreed only by accident, and a wrong layout
reads a neighbouring record's phase -- a coefficient sign flip, not a crash.
And bulk_insert gains a group-prefetched hashed path, shared by the cross-rank
and self inserts.

Tests cover the paths that can disagree: a dense reference implementation the
sparse path is differentially compared against, the digest's tie cases, the
spill boundary where a row exceeds the inline width, and the prefetched insert.
OperatorIndex gains a public overflow_size() so a test can assert the dense and
positional insert paths spill identically -- the claim this change rests on, and
otherwise unreachable from outside the class.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cpp documentation Improvements or additions to documentation python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant