Skip to content

perf(algebra): ⚡ decide the structural cutoff from a (k, d) digest - #234

Closed
diagonal-hamiltonian wants to merge 1 commit into
pr/mpi-lease-stagingfrom
pr/digest-cutoff
Closed

perf(algebra): ⚡ decide the structural cutoff from a (k, d) digest#234
diagonal-hamiltonian wants to merge 1 commit into
pr/mpi-lease-stagingfrom
pr/digest-cutoff

Conversation

@diagonal-hamiltonian

Copy link
Copy Markdown
Collaborator

🤖 AI text below 🤖

Stacked. Based on pr/mpi-lease-staging so the diff shown here is this change alone. It retargets to main as the stack merges.

Summary

The structural cutoffs only ever needed two integers about a monomial: its popcount k, and the number of modes holding both of their Majorana bits, d. Computing them meant calling out to cutoff_sums, which walks the dense words to build a sum it then throws away.

paired_mode_count computes d inline from the dense words and passes_with_popcount decides from (k, d) directly, dropping the popcount <= cutoff early-out. The dense representation is untouched: this changes no layout and moves no data, which is why it is a pure decision change and why its counters must stay bit-identical between arms.

Measured

Default ON. emit_s 0.9200×, 11/12 paired reps over two nodes, sign-test p=0.0063; pooled with 128- and 512-mode shapes 0.9214×, 19/20, p=4.0e-5. Counters (emit/reject/push) bit-identical between arms in all 20 pairs, memory flat.

Worth ~0.8% of build_graphnot the ~3% the microbenchmark projected. In isolation the kernel measures far better than it delivers, because in the real scan the loop is bound by the latency of the random row read rather than by the arithmetic beside it. Quoted at what it delivers.

Measure this on emit_s, not on the wall: at the observed variance the end-to-end benchmark needs ~1590 paired reps to resolve 3%, and the phase timer needs ~5.

The test that matters more than the perf

env_config_member_defaults_match_parsed_defaults.

Every knob declares its default twice — once as a Settings member initialiser, once as parse_flag's fallback in get() — and get() unconditionally overwrites the first with the second. So changing only the member silently does nothing.

That is not hypothetical. It shipped a measured 7.2% regression enabled by default, on a knob that had been "turned off" in the member, in the header comment and in the docs — but not in the fallback. Any knob whose two declarations disagree now fails a test.

Verification

ctest -L serial green; pytest tests --with-mpi green at 1/2/4 ranks.

Checklist

  • Tests added or updated to cover the changes
  • Documentation updated (docstrings, docs/, CONTRIBUTING.md) if needed
  • CHANGELOG / release notes updated if applicable — release notes are generated from the PR title

AI/LLM disclosure

  • I used the following tool to help write this PR description: Claude Code (claude-opus-5)
  • I used the following tool to generate or modify code: Claude Code (claude-opus-5)

The structural cutoffs only ever needed two integers about a monomial:
its popcount k and the number of modes holding both of their Majorana
bits, d. Computing them meant a call out to cutoff_sums, which walks the
dense words to build a sum it then throws away.

`paired_mode_count` computes d inline from the dense words and
`passes_with_popcount` decides from (k, d) directly, dropping the
popcount<=cutoff early-out. The dense representation is untouched: this
changes no layout and moves no data, which is why it is a pure decision
change and why its counters must stay bit-identical between arms.

Default ON. Measured emit_s 0.9200x, 11/12 paired reps over two nodes,
sign-test p=0.0063; pooled with 128- and 512-mode shapes 0.9214x, 19/20,
p=4.0e-5. Counters (emit/reject/push) are bit-identical between arms in
all 20 pairs and memory is flat.

Worth ~0.8% of build_graph, NOT the ~3% the microbenchmark projected --
in isolation the kernel measures far better than it delivers, because in
the real scan the loop is bound by the latency of the random row read
rather than by the arithmetic beside it. Quoted at what it delivers.

Measure this on emit_s, not on the wall: at the observed variance the
end-to-end benchmark needs ~1590 paired reps to resolve 3%, and the
phase timer needs ~5.

`env_config_member_defaults_match_parsed_defaults` is the other half of
this change and matters more than the perf. Every knob declares its
default twice -- once as a Settings member initialiser, once as
parse_flag's fallback -- and get() unconditionally overwrites the first
with the second, so changing only the member silently does nothing. That
shipped a measured regression enabled by default once already, on a knob
that had been "turned off" in the member, in the header comment and in
the docs, but not in the fallback. Any knob whose two declarations
disagree now fails a test.

Assisted-by: ClaudeCode:claude-opus-5
@github-actions github-actions Bot added the cpp label Aug 15, 2026
@diagonal-hamiltonian diagonal-hamiltonian added the test-in-draft Run CI even in Draft mode label Aug 15, 2026
@github-actions

Copy link
Copy Markdown

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

@codecov

codecov Bot commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.70%. Comparing base (130a13b) to head (d29e3a2).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@                  Coverage Diff                  @@
##           pr/mpi-lease-staging     #234   +/-   ##
=====================================================
  Coverage                 97.70%   97.70%           
=====================================================
  Files                        14       14           
  Lines                       742      742           
  Branches                     98       98           
=====================================================
  Hits                        725      725           
  Misses                       12       12           
  Partials                      5        5           
Flag Coverage Δ
cpp 97.70% <ø> (ø)

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

@sonarqubecloud

Copy link
Copy Markdown

@diagonal-hamiltonian

Copy link
Copy Markdown
Collaborator Author

🤖 AI text below 🤖

Closing this stack to rebuild it around a single-path design. Not abandoned — the work returns as fresh PRs once it is ready.

Why. The stack accumulated runtime knobs as a way to A/B competing paths, and an A/B knob is not a shipping knob. Against main's 3 environment knobs the branch reached 15. The codebase already states the rule this violates, in EnvConfig.h:

monoprop_COMPACT_RECORD, monoprop_SPARSE_RESOLVE and monoprop_INSERT_PREFETCH were removed once they had been measured … A knob that has no losing arm left is dead configuration surface.

What the measurements decided. Several paths were being kept behind default-off knobs on the strength of stale or never-run comparisons:

  • Sparse emit + the positions encode measure ~4% slower in emit_s than the dense path at both P=1 and P=8 (6/6 paired reps, p=0.031), with a Pauli structural null confirming the effect is real rather than code layout. The encode optimisation was genuine — 43 ns/push — but it optimised a path that loses by more than it recovers. Both are deleted.
  • Zobrist routing's recorded 2.8% emit_s regression turned out to be stale: it was measured on an emit path that has since been rewritten, and now reads flat. Its disposition is under a P sweep.
  • The query-dump capture and replay tooling chose the wire format and is not load-bearing for any shipped behaviour, so it does not ship.

What returns. The sparse query record together with the positions-based resolve (this PR's successor), the self-resolve simplification, and the layer profiler. Roughly 1,800 lines are deleted rather than added, and every PR in the new stack is a pure addition — the intermediate record format is never introduced, so nothing is added and then removed.

Reopening as new PRs once the local work is verified.

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

Labels

cpp test-in-draft Run CI even in Draft mode

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant