Skip to content

perf(evolution): ⚡ settle a mutual pair from the leader's record alone - #355

Draft
diagonal-hamiltonian wants to merge 3 commits into
perf/stack-5-one-round-joinfrom
perf/stack-6-pair-once
Draft

diagonal-hamiltonian wants to merge 3 commits into
perf/stack-5-one-round-joinfrom
perf/stack-6-pair-once

Conversation

@diagonal-hamiltonian

Copy link
Copy Markdown
Collaborator

🤖 AI text below 🤖

Summary

Two cases the one-round join (PR 5) still did more work than it needed to.

A mutual pair — both endpoints rotating, both records staged on the same slot — was resolved twice:
each record probed the term table for the other's row and pushed its own half. This PR settles it
once, from the leader's record alone (the endpoint without the pivot bit): when the leader's record is
resolved first it already has both rows in hand, so it pushes both halves and the follower's record is
skipped, at its probe as well as at its resolve. TermTable::find_batch takes the caller's skip
predicate and reports BatchStats{hits, skipped}; TableJoin records a skipped query under its own
sentinel, one below the missing row, so the resolve can tell a skip from a miss.

Separately, ContractSink::out_unanswered pushed a 16-byte record for every absent partner, carrying
c0, the partner's pre-gate coefficient — but only the Schrödinger picture has such a coefficient; in
Heisenberg the buffered half is c += sin·(∓1)·0.0, added for nothing. This PR skips it there
(absences_carry_c0, set from schrodinger at the one construction site); GraphSink is unaffected,
since the graph needs the row and the phase regardless of the value.

This is PR 6 of 7, based on perf/stack-5-one-round-join. Both changes are exact, not approximate —
see Measurements. The next PR (perf/stack-7-pair-exchange) replaces the collective exchange with a
direct pairwise one when a gate's window names exactly one peer rank.

Changes

Engine

  • cpp/monoprop/detail/operator/TermTable.h (318 lines): find_batch gains a Skip predicate and a
    skip sentinel, returns BatchStats{hits, skipped}; the existing empty-table early return now also
    consults skip().
  • cpp/monoprop/detail/evolution/layer_build/TableJoin.h (125 lines): kSkippedRow, skipped(q),
    skipped_count(), hits() = confirmed + skipped.
  • cpp/monoprop/detail/evolution/layer_build/Resolve.h (405 lines): sink_pairs_once<Sink>() and
    join_self's pair-once arm (skip-continue on the follower, leader arm settles both halves); one
    join.hit(q) load per query, hoisted above the leader arm but not above the skip test.
  • cpp/monoprop/detail/evolution/layer_build/GateSinks.h (251 lines): ContractSink::pairs_once,
    absences_carry_c0, out_unanswered's skip.
  • cpp/monoprop/detail/evolution/layer_build/GateScratch.h: RowMarks doc on why matched (probe
    time) and received (resolve time) are read separately — the probe runs over the whole gate before
    the resolve does.
  • cpp/monoprop/detail/evolution/layer_build/Engine.h: pair_once = sink_pairs_once<Sink>() && n_self != 0 && base < kSkippedRow; the probe's skip lambda on marks.matched/foll;
    .absences_carry_c0 = schrodinger at the one construction site.

Tests

  • cpp/tests/pair_once_tests.cpp (new, 411 lines): a Gate fixture (20 leader-first pairs, one
    follower-first pair, two silent partners, two absent partners) with halves/marks/rows compared bit
    for bit against a two-record-path oracle; skip-bookkeeping cases for TermTable/TableJoin,
    including the empty-table skip path.
  • cpp/tests/evolution_detail_tests.cpp: .absences_carry_c0 = true on the existing ContractSink
    case, plus contract_sink_skips_the_absence_half_when_no_c0_travels.
  • cpp/tests/sparse_resolve_tests.cpp, term_table_tests.cpp: a never-skip predicate at the
    join/find_batch call sites; term_table_tests pins skipped == 0 on the unaffected paths.

Docs

  • docs/content/docs/features/parallelism.mdx: two paragraphs — the leader-first settlement of a
    mutual pair and its bit-identity argument, and why the absent partner's half is nothing at all in
    the Heisenberg picture.

Measurements

Gated positional (--raw-only) against the x2 raw-bit reference, the same reference PR 5's tail gates
against: gate record md5 543c105e043a329e52ddd3047c8db5c7.

Paired A/B, 3 interleaved reps against origin/main c5e88c8 and the predecessor, ratios only:

rung time st6/mainB peak RSS (kernel) st6/mainB time st5/mainB (predecessor) peak st5/mainB (predecessor)
L1-hubbard 1.096 0.766 1.092 0.763
L1-pauli 0.980 0.777 0.980 0.779
M2a-hubbard 1.078 0.770 1.080 0.768

Pair-once is neutral on one node: every rung is within the noise floor of its predecessor. Its
effect is on the cross-rank pair path, which PR 7 introduces.

Notes for reviewers

  • Exactness argument for the pair-once settlement. The follower's record would have delivered
    {source, -phase, fl(fl(v·cos)·inv_cos)}; the leader's arm instead pushes
    {source, -phase, op_coeffs[row]·inv_cos}. Under the fused sweep, op_coeffs[row] == fl(v·cos) and
    phase_foll == -phase_lead, so the two are the same double; without the sweep both are simply the
    partner's own coefficient. Only in the follower-first order do both records take the ordinary
    (two-probe) arm, so the saving — not the correctness — depends on record order. Mint order is
    unaffected: a skipped query counts as a hit, and hits never mint.
  • Exactness argument for the absence skip. The dropped operation is an addition of an exact zero,
    x + (±0.0). For every finite x != 0 that is x bit for bit; the one observable case is x = -0.0, where -0.0 + 0.0 rounds to +0.0 and skipping the add leaves -0.0 — values that compare
    equal, so this is 0 ULP by any value comparison. The implementer's own dump over the 35 golden cells
    (runs/stack/golden-st6-x2.log) reports the raw diff's +-0only column at 0: no coefficient in that
    set sits at -0.0 where the dropped half would have flipped its sign bit, so the skip is
    bit-identical in fact here, not only 0 ULP on values. The theoretical case (an operator that does
    hold such a -0.0) is a live caveat, recorded in 04c9662's commit message rather than guarded in
    code.
  • Skip counts are deliberately not asserted exactly in pair_once_tests.cpp (only 0 < skipped <= kPairs): PR 7's transport change moves them, and the bit-identity checks against the two-record
    oracle do not depend on the count.
  • TableJoin::live_bytes() stays absent and no TimeProf counters (n_mutual, n_skipped) are
    added: diagnostics stay out of this stack, per the decision recorded in PR 2's Notes.

Checklist

  • Tests added or updated to cover the changes
  • Documentation updated (docstrings, docs/, CONTRIBUTING.md) if needed
  • CHANGELOG / release notes updated if applicable (n/a — the repository has no CHANGELOG)

AI/LLM disclosure

  • I did not use LLM tooling, or used it only privately for ideation
  • I used the following tool to help write this PR description: Claude Code (claude-sonnet-5)
  • I used the following tool to generate or modify code: Claude Code (claude-opus-5)

Important

By opening this PR I confirm that I have read CONTRIBUTING.md and I agree to the terms of the Contributor License Agreement.

Warning

If you're contributing on behalf of your employer, contact cla@algorithmiq.fi to arrange a Corporate CLA.

…eisenberg

`ContractSink::out_unanswered` is called once per record whose key missed everywhere -- once per
mint of every gate -- and pushed a 16-byte `HalfRotationRec` carrying `c0`, the absent partner's
pre-gate coefficient. Only the Schrödinger picture has such a coefficient: `sent_c0` is windowed
exactly when the scan was handed a `state_mask`, and `absence_pass` itself substitutes the literal
0.0 otherwise. In Heisenberg the buffered half is therefore `c += sin·(∓1)·0.0` -- 16 bytes in the
halves buffer, a slot in the gate's reserve, and one random-access read-modify-write of a
coefficient the apply would not otherwise touch, to add nothing.

Skip it there. The sink carries `absences_carry_c0`, set from `schrodinger` at the one construction
site; the Schrödinger path is untouched.

ROUNDING ORDER: the dropped operation is an addition of an exact zero, `x + (±0.0)`. For every
finite x != 0 that is x bit for bit, and for x = +0.0 it is +0.0. The single observable difference
is x = -0.0, where `-0.0 + 0.0` is `+0.0` and skipping the add leaves `-0.0`; those compare equal,
so it is 0 ULP by any comparison of values. Measured on the 35 golden cells, no coefficient sits
there: the raw dump's `+-0only` column is 0 and the arm is bitwise identical.

`GraphSink::out_unanswered` still records every absence -- the graph needs the row and the phase
regardless of the value -- the halves reserve is an upper bound and stays valid, and the absence
pass's own iteration order is unchanged.

Assisted-by: ClaudeCode:claude-opus-5
A mutual pair -- both endpoints rotating, both records staged on the same slot -- was resolved
twice: each record probed the term table for the other's row and pushed its own half. When the
LEADER's record (the endpoint without the pivot bit) is resolved first it already has both rows in
hand, so it pushes both halves and the follower's record is skipped, at its probe as well as at its
resolve. One probe, one confirm and one resolve pass per local mutual pair.

Exact, not approximate. The follower's record would have delivered {source, -phase,
fl(fl(v·cos)·inv_cos)}; the leader's arm pushes {source, -phase, op_coeffs[row]·inv_cos}, and the
fused sweep's invariant op_coeffs[row] == fl(v·cos) plus phase_foll == -phase_lead make the two the
same double. Without the sweep both are the partner's own coefficient. In the follower-first order
both records take the ordinary arm, so only the saving depends on the order. Mint order is
untouched: a skipped query counts as a hit, and hits never mint.

`TermTable::find_batch` takes the caller's skip predicate and reports `BatchStats{hits, skipped}`;
`TableJoin` records a skipped query under its own sentinel, one below the missing row, so the
resolve can tell a skip from a miss. The probe-time test reads `matched` and the resolve-time one
`received`: the probe runs over the whole gate before the resolve does, which is why the two marks
are kept apart. `ContractSink` opts in with `pairs_once`; the graph sink, having no values to
recover, does not.

Also here, one load per self query: `join.hit(q)` is read once for both arms instead of once per
arm.

Assisted-by: ClaudeCode:claude-opus-5
Two paragraphs in the parallelism page's protocol section: the leader-first settlement of a mutual
pair on one slot, with the bit-identity argument and what the follower-first order costs, and why
the absent partner's half is nothing at all in the Heisenberg picture.

Assisted-by: ClaudeCode:claude-opus-5
@coderabbitai

coderabbitai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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.

@github-actions github-actions Bot added documentation Improvements or additions to documentation cpp labels Sep 10, 2026
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant