Skip to content

refactor(parallel)!: ♻️ replace the partition runtime with sharded OpenMP - #398

Open
robertodr wants to merge 67 commits into
mainfrom
refactor-parallelization
Open

robertodr wants to merge 67 commits into
mainfrom
refactor-parallelization

Conversation

@robertodr

Copy link
Copy Markdown
Member

🤖 AI text below 🤖

Summary

Replace the custom partition-worker runtime with a single sharded OpenMP implementation.

Each MPI rank owns one shard per OpenMP worker, with owner-local mutation and operation-scoped execution. Only MPI’s initializing thread performs communication, reducing the required support level from MPI_THREAD_SERIALIZED to MPI_THREAD_FUNNELED.

This retains sharded storage and the existing numerical policy while removing custom worker management, in-process communicators, topology discovery and thread pinning.

Changes

  • Implement sharded graph construction, propagation, replay, energies, gradients and paring, preserving source ordering, snapshots and ascending-shard reductions.
  • Replace partition collectives with explicit local phases and checked physical-MPI exchanges. Preserve request lifetimes and invalidate propagators after failures that leave partially mutated state.
  • Reduce allocation and communication overhead through reusable staging and paring buffers, owner-local replay layouts, single-owner fast paths and Linux spare-capacity page release.
  • Remove PartitionGroup, CpuTopology, ShmComm, HybridComm, custom partition barriers and the temporary runtime selector.
  • Require OpenMP in every build and exported CMake package; remove direct hwloc/pkg-config dependencies. Update native, wheel, Nix and CI configuration.
  • Add numerical, ownership, failure-injection, MPI thread-support, sanitizer and installed-consumer coverage.
  • Add reproducible runtime/memory comparison tooling with binary identity, placement validation and a frozen 250-cell workload inventory.
  • Update public API documentation, launch guidance and developer workflows.

Changeset size by folder

Diff against the merge-base with origin/main (90d5717 → 747b562):
169 files changed. Counts include source, tests, documentation and configuration—not executable code alone.

Folder Lines added Lines removed
Repository root 147 22
.devcontainer/ 1 0
.github/ 87 47
benches/ 136 37
cmake/ 7 0
cpp/include/ 229 219
cpp/monoprop/ 9,238 4,413
cpp/tests/ 16,534 2,797
docs/content/ 476 87
docs/superpowers/ 5,234 0
nix/ 10 10
packages/ 709 9
src/ 32 12
tests/ 4,289 5
tools/ 3,614 50
Total 40,743 7,708

Breaking changes and migration

  • The C++ constructor loses its partition count and child factory; the low-level Python binding also loses partitions. Rebuild C++ consumers and bindings together with the library.
  • monoprop_PARTITIONS is no longer read. Each propagator captures monoprop_NUM_THREADS at construction, falling back to the OpenMP default when unset; copies retain that budget.
  • Launch with a uniform thread count across ranks, OMP_DYNAMIC=FALSE and sufficient allocated cores. Placement belongs to the launcher/OpenMP, not monoprop.
  • Raw C++ storage/graph accessors require one shard (T=1); otherwise they throw MultiShardUnsupported. Rank-local aggregate APIs cover every local shard.
  • MPI-using calls must originate on MPI’s initializing thread, even when MPI provides a higher support level.
  • OpenMP is mandatory; MPI remains optional and disabled by default.

Recorded verification

The latest implementation records include:

  • CTest: 481 MPI-OFF tests and 534 MPI-ON tests.
  • Python suites at T=1/2/4, two-rank runs at T=1/2, and MPI-specific coverage on 1/2/4 ranks.
  • Fixed-geometry bitwise differential checks against pre-cutover/control builds.
  • GCC ASan/UBSan, Python ASan at T=1, and a 153-case Clang/libomp/Archer TSan selection.
  • Installed consumers, FUNNELED acceptance, insufficient-support rejection and bounded distributed-failure tests.

Nix, macOS, Linux aarch64, CI wheel builds, minimum-version compilers and multi-node qualification remain pending. TSan coverage of Open MPI’s own MULTIPLE-level locking remains inconclusive.

Final performance campaign — running

The final campaign is currently running on a single AWS EC2 c8a.metal-24xl bare-metal node:

  • CPU: AMD EPYC 9R45, fifth-generation EPYC (“Turin”).
  • CPU capacity: 96 physical cores / 96 vCPUs, one hardware thread per core; no SMT.
  • RAM: 192 GiB instance capacity; usable memory is lower after system overhead.

The campaign covers the frozen 250-cell inventory, including actual MPI-OFF execution and MPI+OpenMP geometries on that node. This is single-node qualification, not evidence of multi-node or HPC-interconnect scaling.

The pre-cutover campaign completed all 250 cells with matching numerics, but only 102/250 passed all five runtime/memory gates. The owner authorized proceeding with removal without a passing pre-removal campaign; the final gates were not relaxed.

Subsequent profiling-driven changes improved targeted workloads, but those diagnostics do not establish full-library parity. The running campaign evaluates the final post-cutover binaries, with matched page-cache conditioning and fresh baseline samples for memory comparisons.

Final acceptance remains pending the campaign results: every required cell must independently satisfy runtime, operation peak sum/max and construction peak sum/max ratios ≤1.00, alongside numerical validation.

See the implementation plan for detailed evidence, decisions and outstanding qualification.

Checklist

  • Tests added or updated to cover the changes
  • Documentation updated
  • CHANGELOG / release notes updated if applicable

The standing lint waiver is limited to the 49 pre-existing Ruff findings in the three documented notebooks.

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: Pi
  • I used the following tool to generate or modify code: Pi, using multiple models as recorded in commit trailers

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.

robertodr and others added 30 commits September 23, 2026 14:38
Standalone CMake experiment for Task 0 of the rank-local OpenMP plan. One process and one OpenMP team compare
three ownership/index designs over the current packed OperatorIndex rows: per-thread disjoint stores,
one shared store with frozen parallel lookup and caller-owned insertion, and one shared packed backing
store indexed by boost::concurrent_flat_map with per-query or bulk lookup.

It reuses the header-only storage/hash code without changes and does not link or import monoprop.
Release-active self-tests cover exact IDs, sharded owner/local IDs, collisions, spilled rows, tails,
duplicate-miss rejection, runtime-limited teams, worker participation and joined exceptions.
A BOOST_UNORDERED_ENABLE_STATS diagnostic build adds Boost's own container statistics to the CSV output.

Assisted-by: Pi:claude-opus-5-5
…cision

Record the owner's decision after Task 0: the evidence was inconclusive for the ownership axis, so one
store per rank remains the working hypothesis. Summarize the mini-app, in-library counter, one-core
profile and Boost-index trial results, and record the waiver of Boost 1.85 compile checks.

Move the ownership decision to Task 11's full-library comparison against the partition runtime, add a
diagnostic go/no-go after Task 6 before the Task 10 API cutover, and make Task 12 conditional on
confirming the single store. Replace the bounded Boost trial with a shared-serial versus
shared-concurrent index A/B, and add an invariant that keeps OperatorIndex swappable.

Assisted-by: Pi:claude-opus-5-5
Task 1 measurement layer for the rank-local OpenMP plan, applied identically to both arms.

- benches/conftest.py records operation and outer memory-window exactness independently (all-rank
  reductions), imports mpi4py only for MPI-enabled extensions, records has_mpi and the extension
  hash, and adds --runtime-shape/--build-mode preflight while keeping the legacy multi-rank guard.
- monoprop_bench_tools.preflight holds the shape/build-mode checks shared with the driver; the
  report and BMF renderers label or gate each memory metric by its own window's flag.
- tools/benchmark-rank-local-openmp.py has exactly three modes: observe (historical pytest nodes
  in-process, the new bounded driver::pare_functional_construct measurement, and a separate
  whole-construction worker), validate (untimed numerical products with gzip JSON Lines term
  files), and compare (frozen-inventory joins, placement checks and the five median gates).
- Docs describe the options, exactness labels, the VmHWM shortfall observed on many-core hosts,
  and the driver.

Assisted-by: Pi:claude-opus-5-5
Record the owner's decisions after the unscored baseline pilot and freeze the campaign.

- tools/rank-local-openmp-campaign.json (250 cells) and tools/rank-local-openmp-workloads.json
  are the owner-approved inventory: tiny/reference (+pared) across MPI 1x1, 1x96, 2x48 and MPI-off
  1x96, reference routing cells at 3x32/4x24, and large (+pared) at MPI 1x96, 2x48 and MPI-off 1x96.
- Term files are gzip-compressed JSON Lines instead of adding storage (4.4-16x smaller measured).
- Hubbard graph cells use 2 Trotter steps within the size guard; the 29-step graph did not fit.
- The measured VmHWM shortfall is accepted as documented measurement noise; gates stay <=1.00.
- prek exempts the frozen workloads file, which carries full parameter vectors by design, from
  the large-file check.

Assisted-by: Pi:claude-opus-5-5
Name the host directories that hold the uncommitted Task 0 results and the Task 1 handoff report,
formal baseline evidence, baseline index, collection scripts and pilot, so Task 11 can locate the
baseline half of its comparison. The artifacts stay uncommitted and will be removed before merging.

Assisted-by: Pi:claude-opus-5-5
…ire OpenMP

Task 2 of the rank-local OpenMP plan: the smallest worksharing helper and the mandatory OpenMP
dependency across build routes. No engine code uses the helper yet.

- detail/parallel/Options.h holds the per-region thread budget and does not include omp.h.
  detail/parallel/Workshare.h::for_blocks runs each logical block once:
  - it rejects non-positive budgets and counts above PTRDIFF_MAX before any work;
  - it runs serially in ascending order for a budget or count of one, or inside any OpenMP region;
  - otherwise it opens one static-schedule region requesting min(threads, count) workers;
  - worker exceptions stay in per-worker slots and are rethrown on the caller after the join;
  - it changes no OpenMP runtime setting.
- CMake requires OpenMP CXX in every configuration, links OpenMP::OpenMP_CXX PUBLIC on both
  monoprop-objs and the shared monoprop target (which is built from TARGET_OBJECTS), and the
  package config calls find_dependency(OpenMP).
- openmp_workshare_tests.cpp covers:
  - exactly-once visits for counts 0/1/19 and budgets 1/2/3, and more workers than blocks;
  - serial and nested fallbacks, and budget and bound validation;
  - unchanged runtime settings and joined worker exceptions;
  - observed multi-worker participation, skipped when the runtime cannot give two workers.
- The shared link-export probe, which just test-find-package also builds, instantiates for_blocks,
  requires the imported target's OpenMP flags and drives the Pauli basis as well as Majorana.
- Dependency routes:
  - Homebrew libomp with OpenMP_ROOT, in the setup action, the macOS wheel config and the docs;
  - a matching libomp-<ver>-dev for Linux Clang lanes, and libomp-dev in the devcontainer;
  - llvmPackages.openmp for Clang-based Nix builds, monoprop-mpi in the flake checks, and a
    dev-shell consumer step in nix.yml;
  - clang-tidy's setup now receives libomp.
  Hwloc stays until its removal task.

Assisted-by: Pi:claude-opus-5-5
Record what Task 2 delivered and what later tasks inherit:
- the helper's implemented contract: exception types, serial and nested fallback conditions, and
  per-worker stop-on-first-failure;
- RED/GREEN and participation evidence under libgomp and libomp, and the load-bearing
  find_dependency;
- the observation that GNU ld's --as-needed drops libgomp while the engine has no OpenMP code, so
  wheel runtime bundling is still unverified;
- which dependency routes were verified on Linux x86_64 and which await CI or a suitable host;
- a build-route note: rebuild through uv sync, since the adopted tree's cached ninja is gone, and
  keep M and Clang trees in their own venvs;
- where the uncommitted Task 2 artifacts live.

Assisted-by: Pi:claude-opus-5-5
…lure boundaries

Task 3 of the rank-local OpenMP plan: configuration plumbing and safety boundaries. Kernels stay
serial.

- detail/parallel/ThreadBudget.{h,cpp} (exported, installed):
  - resolve_thread_budget is a pure parser. Unset monoprop_NUM_THREADS takes a positive
    omp_get_max_threads(); a set value must be ASCII digits in [1, INT_MAX], with leading zeros
    accepted. Empty, signed, padded, list, zero and overflowing values are rejected. There is no
    thread cap.
  - capture_thread_budget reads the environment once per call.
- MonomialPropagator gains a private parallel::Options. It is serial except on the one-store
  prototype (explicit partitions=1 on an ordinary communicator), which captures the budget once at
  construction. Copies and retained functionals keep it; Shm/Hybrid children and facades stay
  serial. An invalid budget is a PropagatorConfigError on one rank and aborts on several.
- The trailing Options reaches every path: graph-only build, stored-mask callbacks, immediate fused
  application, replay, retained functionals (EvalRequest::parallel), prepare_evolved_operator, the
  derivative (after record) and partial contraction. build_cos_callbacks closures capture it and the
  callback types are unchanged.
- Failure boundaries:
  - mpi::operation_failed reports the error and calls MPI_Abort on a multi-rank ordinary
    communicator, and rethrows the original exception otherwise.
  - Guards sit inside the replay Ticket and construction PendingAlltoallv lifetimes, and around
    local work before each allreduce.
- Invalid owners: each operation separates validation from mutation. A failure after mutation
  starts invalidates the object. Later operations, copy/clone and retained functionals then throw
  InvalidPropagatorError; earlier copies keep working. Interleaved index validation now runs first.
- MPI: the requested and required level stay MPI_THREAD_SERIALIZED, and the level actually
  provided is checked for host- and library-initialized MPI. require_initializing_thread
  (MPI_Is_thread_main) makes prototype entries from another thread print a diagnostic and abort
  locally, with no MPI call.
- Tests:
  - openmp_runtime_tests.cpp, plus fresh-process openmp_env_* CTest entries;
  - tests/test_openmp_config.py and a routes_pairwise cache case;
  - mpi_failure_driver.cpp, outside the unit runner: eight two-rank mpi_failure_* scenarios with a
    30 s timeout, covering worker throw, before exchange, both request owners, insufficient level
    and wrong thread;
  - the link-export probe now covers the prototype chain.
- Docs cover the prototype, the failed-operation rules, the ABI rebuild, OpenMP runtime linkage and
  the new test entries.

Assisted-by: Pi:claude-opus-5-5
monoprop now links the OpenMP runtime. With OMP_PLACES set and binding on, libgomp binds the
importing thread to the first place when the library loads, so the observe preflight read one CPU
instead of the allocation. It then rejected the documented candidate launch on GCC builds.

The allocation is now read before the import. For the baseline binary, which loads no OpenMP
runtime, both orders read the same mask, so collected evidence is unaffected.

Assisted-by: Pi:claude-opus-5-5
Record what Task 3 delivered and what later tasks inherit:
- the implemented budget, invalidation, failure-boundary and MPI thread contracts;
- RED/GREEN and mutation evidence, the R/M/Clang results and the provided MPI thread levels;
- the ABI change, and the OpenMP runtime that the library now links;
- the harness allocation fix, and the matching edit still due in the baseline worktrees;
- where the uncommitted Task 3 artifacts live.

Assisted-by: Pi:claude-opus-5-5
…check

The allocation fix is now applied to the tool copy in both baseline worktrees, so both arms run the
same measurement code (sha256 2207529f…). Task 11 gains a checklist item to verify that hash before
measuring, because those copies are uncommitted and could drift.

Assisted-by: Pi:claude-opus-5-5
Task 4 of the rank-local OpenMP plan. Two kernels now use the object's thread budget. Structure (scan,
lookup, insertion, graph packing) stays serial.

- scale_cos_lazy runs one logical range per 1024 fold words. Row parity is fetched on the caller for
  each call. Each worker keeps its scratch on its own stack, never the caller's thread_local buffer.
  Tail masking stays global.
- scale_cos_mask runs one range per 1024 stored mask blocks.
- apply_fused_contract keeps its phases, each joined before the next: gather insert snapshots into
  the existing v_tgt field, run the optional cosine phase, then apply records in ranges of 1024.
  - Every record range calls one out-of-line apply_fused_record_range, so the compiler's FMA
    contraction of cos*c + sin*phi*v is the same at every budget. Serial and threaded results are
    bitwise equal, and match the previous release bitwise on the paths checked.
  - Debug builds assert that every coefficient slot has exactly one writing record.
- Fewer than two ranges stays serial. The thresholds are fixed until the evidence-driven tuning task.
- The kernels take a defaulted, test-only range observer; production passes an empty one.

Tests:
- openmp_kernel_tests.cpp covers exact agreement across budgets, small work staying serial, worker
  exceptions joined before rethrow, and participation observed inside each kernel's own ranges.
- Synthetic multi-block lazy folds (tails, cold parity, Majorana/Pauli, empty columns, index growth)
  are checked against the materialised-fold oracle.
- Records from the real build path have one add-owner per slot, in both pictures and with cross-rank
  halves under MPI. Threaded prototypes match serial ones bitwise.
- Fused and two-pass application still agree with graph replay at the existing tolerances.
- openmp_env_kernels_* rerun the kernel cases under OMP_THREAD_LIMIT=2 and OMP_DYNAMIC=TRUE.
- The failure driver gains active-ticket cosine-worker: a kernel worker throws while a replay
  Ticket is posted, and the job must abort rather than hang.

Docs: the one-store prototype section lists which kernels are threaded. building.mdx explains
that TSan evidence for the OpenMP kernels needs Clang + libomp + Archer, since GCC's libgomp reports
false races.

Assisted-by: Pi:claude-opus-5-5
…aluation

Record what Task 4 delivered and what later tasks inherit:
- the kernel range, scratch, phase and FMA-determinism contracts, and the test-only observer;
- RED/GREEN, mutation, stress and sanitizer evidence, with the qualified TSan runtime;
- the owner-approved replay diagnostics: one-store speedups and losses, kernel-only scaling, and the
  comparison with the baseline partition runtime;
- where the uncommitted Task 4 artifacts live.

Task 9 gains a checklist item to evaluate explicit std::fma against the current contraction-dependent
arithmetic, before any numerical-policy change.

Assisted-by: Pi:claude-opus-5-5
Task 5 of the rank-local OpenMP plan. The per-gate bitmap scan that finds anticommuting terms and emits
their partner queries now uses the object's thread budget. Lookup, insertion, publication and graph
packing stay serial.

- fused_find_and_collect prepares the inverted index and row parity on the caller, then splits the
  words into min(threads, ceil(words/1024)) contiguous runs of whole fold blocks (scan_ranges,
  scan_range_words). Range IDs depend on the word count and budget only, never on the actual team.
- Each range runs fused_find_and_collect_range into its own FusedScanResult. It never calls a lazy
  accessor, keeps its scratch private or thread_local on the executing thread, masks only the global
  tail word and, in the fused sweep, scales only its own sources, once, after capturing them.
- merge_scan_ranges joins the pieces in range order, stream by stream: checked totals, one
  reservation, the first buffer moved where it can hold the stream, drained buffers released. Self
  stages merge through SelfQueryStage::push. Output is identical to the serial scan at every budget.
- CutoffEvaluator::parallel_safe() admits only the typed length and support cutoffs. Opaque
  predicates, basis-change closures and scans below two ranges keep the serial scan on the caller.
- The test-only range observer moves to layer_build/Common.h, gains KernelRange::scan, and
  build_layer forwards a defaulted one; production passes none.

Tests:
- ScanTestSupport.h compares threaded scans with budget one byte for byte across windows (one rank,
  linear zero/non-zero shifts with non-zero bases, dense splitmix at three and four ranks), pivots,
  Majorana and Pauli J(G), caps, cutoffs, atol boundaries, capture, the fused sweep and cold caches.
- The merge is tested on hand-built pieces: variable-length wire records, both self stages, an empty
  piece, buffer moves and released capacity.
- Full construction at budgets 1-4 gives identical rows, row IDs, graph layers and coefficients.
- Opaque and basis-change cutoffs stay serial with identical results.
- A scan worker failure joins before rethrow and invalidates the propagator; the failure driver
  gains before-exchange scan-worker, which must abort both ranks rather than hang.
- openmp_env_kernels_* rerun the scan cases under OMP_THREAD_LIMIT=2 and OMP_DYNAMIC=TRUE.

Docs: the one-store prototype section lists the threaded scan and its serial cutoff fallback.

Assisted-by: Pi:claude-opus-5-5
Record what Task 5 delivered and what later tasks inherit:
- the scan range, merge, cutoff-classification and observer contracts;
- RED/GREEN, mutation, stress, sanitizer and cross-revision evidence;
- the owner-approved construction diagnostics: traversal and end-to-end speedups, the losses at high
  budgets, the growing serial merge, and the scratch and peak-memory accounting;
- where the uncommitted Task 5 artifacts live.

Assisted-by: Pi:claude-opus-5-5
Task 6 of the rank-local OpenMP plan, committed as the record of the one-store experiment (the owner
decided afterwards to re-design and keep the sharding). Only the prototype selected by explicit
partitions=1 on an ordinary communicator uses these threaded paths.

- probe_frozen_positions: 256-query blocks, each one find_batch_positions call over absolute offsets
  into the shared position buffer with sliced counts, outputs and hashes; span contracts checked before
  any block runs; the store stays frozen until every worker joins.
- probe_incoming_queries validates every sender stream on the caller with QueryWire::checked_extent
  (ternary phase, k and gap-width bounds, canonical escaped counts, whole records including the fused
  value word, final offset at the buffer end), checks prefixes and allocates every array once. Workers
  decode disjoint blocks; read_positions rejects size_t positions >= 2N before narrowing. Misses get
  IDs serially in sender-window/query order after a row-ID ceiling check; miss_g is vector<size_t>.
- resolve_incoming scatters on workers only for sinks declaring parallel_resolve (GraphSink,
  Heisenberg ContractSink); Schrodinger scoring and generic sinks stay serial. It runs inside
  guard_distributed, as the peers may be in the response round.
- The self lookup probes windows of at most 4096 queries; filtering, gathering and emission stay
  serial and nothing is inserted between windows. Publication order and the single-writer
  grow/set_positions/bulk_insert_hashed/reindex sequence are unchanged; bulk_insert_hashed gains an
  ignored trailing parallel::Options seam.
- The test-only observer gains KernelRange::{decode,incoming_probe,self_probe,scatter}.

Tests:
- Serial/threaded bitwise equality of probes, decoded positions, hashes, predicted and published IDs,
  row order, responses, sink records, marks and rebuilt inverted indices, over empty/all-hit/all-miss/
  mixed inputs, 255/256/257-query boundaries, empty senders, non-zero window bases, narrow and wide
  positions, spilled rows, escaped counts, Plain/Fused codecs and 4096-query self windows.
- Malformed streams (including the noncanonical escape 0x8037e) rejected before decode; invalid
  positions rejected in decode before probing; serial scatter for uncertified sinks; duplicate-key
  bulk insertion pinned; participation observed inside each new region; four new two-rank
  resolve-worker failure scenarios abort promptly.

Assisted-by: Pi:claude-opus-5-5
… sharding

Record the Task 6 outcome and the post-Task-6 architecture go/no-go:
- the Task 6 contracts, RED/GREEN, mutation, stress, sanitizer and cross-revision evidence;
- the phase diagnostic, including the libgomp high-budget regression from small self-probe teams;
- the architecture diagnostic: parity at L1, 0/16 reference cells at runtime <= 1.00 at full
  machine (2-62x), and no credible bounded route to parity;
- the owner's decision: the one-store design is abandoned before the Task 10 cutover and the
  architecture is re-designed keeping the sharding. Tasks 7-13 are marked superseded.

Assisted-by: Pi:claude-opus-5-5
Document owner-local sharding with operation-scoped OpenMP teams and primary-thread MPI. Supersede the one-store architecture while preserving its history and frozen evidence.

Keep arithmetic ordering, shard combination, reductions and FMA policy open for separately approved post-landing optimizations, individually or together.

Documentation checks pass. Full prek run reports 49 pre-existing notebook Ruff findings, waived by the owner for this documentation-only push.

Assisted-by: Pi:gpt-6-astra
Task S0 of the sharded OpenMP plan: detail/sharded/Team.h, a header-only operation-scoped team and
checkpoint-stable failure protocol. Nothing in the propagator uses it yet.

- run_team opens one omp parallel num_threads(T) region, calls a noexcept orchestration body once per
  worker with its fixed OpenMP index, and returns the lowest failing worker's original exception after
  the join instead of rethrowing it, so future callers can handle failures while MPI requests and
  buffers are still alive.
- phase catches everything into the worker's own preallocated slot and always reaches the checkpoint:
  barrier, masked primary decision, barrier. A decision cannot be overwritten until every worker has
  passed the next leading barrier, so a fast worker's later failure cannot change an earlier branch.
- The helper assumes exactly T workers: no team-size check, fallback, runtime-setting change, MPI or
  persistent state. Options.h now documents that reduced-team correctness belongs to for_blocks.
- sharded_team_* cases run in fresh sharded_team_env_t{1,2,4} and _t4_passive launches; the installed
  consumer probe instantiates Team.h through monoprop::monoprop.

Verified with GCC/libgomp and Clang 18/libomp (MPI off), ASan/UBSan, MPI on, Clang/libomp/Archer TSan
and 13 Team.h mutants. prek passes apart from the 49 pre-existing notebook Ruff findings the owner
waived for this plan.

Assisted-by: Pi:claude-opus-5-5
Mark Task S0 of the sharded OpenMP plan done and accepted by the owner, with a summary of its RED,
GREEN, mutation, sanitizer and installed-consumer evidence and the platforms still pending.

Record the owner's standing waiver for the whole execution of the plan: the pre-existing Ruff findings
in the chemistry, fermi_hubbard and kicked_ising notebooks (49 at S0) are ignored. The waiver covers no
other file, hook or new finding, and the notebooks are not to be edited to silence it.

Assisted-by: Pi:claude-opus-5-5
Task S1 of the sharded OpenMP plan: detail/sharded/State.h, internal shard state that can be seeded,
copied, counted and destroyed without a worker pool or child propagators. The public runtime is
unchanged.

- ShardState holds the existing MPOperator, MPGraph and MatchedEpochSet. make_shards allocates only
  the pointer vector on the caller, initializes each shard on its owner inside an S0 protected phase,
  and rethrows the lowest failing owner's exception after the join, once published siblings have
  been destroyed.
- Seeding is split into caller-side preparation (validate_initial_operator, paired_basis_bounds,
  packed_inline_width, OperatorSeed) and the owner-local seed_operator. That function preserves input
  and paired-basis order, flat owner rank * T + shard, per-owner reserve, pending entries and the
  Heisenberg-sparse versus Schrodinger-dense cache warm-up. The legacy constructor now seeds through
  it, so behaviour is unchanged.
- copy_shards deep-copies each shard on its owner and keeps graph layer cores shared. Per-shard counts
  and accounting need no merged operator.
- MPOperator() is no longer noexcept: its store initializer allocates, and an injected allocation
  failure previously terminated the process.
- sharded_state_* cases run in fresh sharded_state_env_t{1,2,4} launches, against the legacy partition
  oracle and an independent reference. A test-only allocation probe fails every allocation site of the
  seed, copy and legacy-constructor paths in turn. The installed consumer compiles State.h on its own.

Verified with GCC/libgomp and Clang 18/libomp (MPI off), ASan/UBSan, MPI on, Clang/libomp/Archer TSan
and 11 mutants. prek passes apart from the 49 notebook Ruff findings already waived for this plan.

Assisted-by: Pi:claude-opus-5-5
Mark Task S1 implemented, with owner acceptance still pending. Summarize its interfaces, the MPOperator
noexcept fix, and its RED, GREEN, mutation, sanitizer and installed-consumer evidence. List the
platforms still pending. S2 still needs its own authorization.

Assisted-by: Pi:claude-opus-5-5
Task S2 of the sharded OpenMP plan: detail/sharded/Construction.h, an internal P=1, T-shard engine for
multi-gate graph construction and graph-free propagation. The public runtime is unchanged.

- build_graph and propagate run the whole gate loop in one operation-scoped team. Worker t mutates
  only shard t. Other shards' published query and answer buffers are read as immutable spans after
  the checkpoint that published them, in ascending flat-source order. There are no communicators, no
  custom barriers and no nested worksharing, and within-shard kernels run serially.
- Per gate: traversal and leader prepare, leader resolution, leader consume and follower prepare,
  follower resolution, then follower consume and finish/apply. The row order, follower filtering,
  frozen probes, deferred same-shard inserts, scaled_count and every arithmetic path are preserved.
- Opaque cutoffs and basis-change closures traverse every shard on the primary, in an exclusive phase
  that every worker enters.
- Phase failures are returned after the join as ConstructionOutcome{error, mutation_started}, so the
  S4 caller can invalidate the owner; argument errors throw before the team.
- LayerBuildEngine gains prepare_exchange, resolve_published and consume_published. The traversal is
  extracted as scan_gate. The legacy transport is now the run_exchange(comm, plan, ...) adapter over
  the same phases, with unchanged request lifetimes and guards. The resolver and the sinks take
  read-only views (mpi::views_of) instead of owning buffers.
- QueryWire::Writer::put/flush are no longer noexcept: they allocate, and an injected allocation
  failure while encoding a query previously terminated the process.
- sharded_construction_* cases run in fresh sharded_construction_env_t{1,2,4} launches. They check
  bit-identical shards and streams against the legacy partitions at (1, T). They also check
  independent insertion-order and coefficient-map references, the frozen exact energy, cross-T
  retained maps, participation, and failures in every phase including allocation sweeps. The
  installed consumer compiles Construction.h on its own.

Verified with GCC/libgomp and Clang 18/libomp (MPI off), ASan/UBSan, MPI on (legacy compatibility),
Clang/libomp/Archer TSan and 11 mutants. prek passes apart from the 49 notebook Ruff findings already
waived for this plan.

Assisted-by: Pi:claude-opus-5-5
Mark Task S2 implemented, with owner acceptance still pending. Summarize its interfaces, the phase
sequence, the QueryWire noexcept fix, and its RED, GREEN, mutation, sanitizer and installed-consumer
evidence. List what S2 leaves to S3–S5 and the platforms still pending. S1's acceptance record is
unchanged. S3 still needs its own authorization.

Assisted-by: Pi:claude-opus-5-5
Task S3 of the sharded OpenMP plan: detail/sharded/Evaluation.h, P=1 T-shard replay, energy,
reverse-mode gradients, partial contraction and retained/pared functionals, plus coefficient-informed
graph construction. The public runtime is unchanged.

- ev_sharded and ev_and_grad_sharded run the whole forward and reverse replay in one team. The
  communicator is touched only on the caller, and a phase failure goes to mpi::operation_failed
  before the result reduction. evaluate_shards is the S4 seam: per-shard terms and gradients plus
  the error, returned after the join.
- Each replay step publishes pre-cosine endpoint snapshots into a double buffer, so partners never
  read live coefficients and each step costs one checkpoint. Self pairs, records, predivide/restore
  order and repeated-parameter accumulation are unchanged. Shard terms and gradient components are
  folded in ascending shard order, and the identity is added once.
- The kernels move to the library-internal detail/evolution/LayerReplay.h, shared with the rewired
  legacy Evolution.cpp and MPFunctions.cpp. The legacy derivative stays inline so its contraction is
  unchanged; public results are bit-identical to the previous build (GCC, Clang, MPI), and the
  sharded path matches legacy bitwise.
- Library-built callbacks are marked CosCallbacks::owner_parallel and run on their owners. Opaque
  callbacks run on the primary in an exclusive phase.
- build_graph_informed replays the seed and each new layer inside the construction team.
  pare_graph_owner takes the flat owner instead of a communicator rank. prepare_retained snapshots,
  pares and builds callbacks per shard.
- sharded_evaluation_* cases run in fresh sharded_evaluation_env_t{1,2,4} launches. They check
  bitwise equality with the legacy partitions, independent map, closed-form and finite-difference
  references (including the deep-circuit gradient cases), layouts, lifetimes, participation and
  failures. The installed consumer calls both evaluators.
- Also reformats three S2 files that were not clang-format clean (whitespace only).

Verified with GCC/libgomp and Clang 18/libomp (MPI off), ASan/UBSan, MPI on (legacy compatibility),
Clang/libomp/Archer TSan and 14 mutants. prek passes apart from the 49 notebook Ruff findings already
waived for this plan.

Assisted-by: Pi:claude-opus-5-5
Mark Task S3 implemented, with owner acceptance still pending. Summarize its interfaces, the
double-buffered snapshot protocol, the contraction-fidelity fix, and its RED, GREEN, mutation,
sanitizer and installed-consumer evidence. List what S3 leaves to S4–S5 and the platforms still
pending. S4 still needs its own authorization.

Assisted-by: Pi:claude-opus-5-5
… protocol

Update the sharded OpenMP plan with S8's optimization rounds (steps 0-2, rounds 3 and 4): what
was kept, what was dropped and why, the page-cache finding behind the MPI-off code-page excess
and the MPI memory drift, the owner's campaign protocol (page-cache eviction and one warm-up per
arm before each batch, interleaved arms, memory against fresh baseline samples), the re-measured
gates, the LTO measurement, what stays open, and the off-host move of S7's raw term files. The
final parity campaign still awaits authorization.

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

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 180 files, which is 30 over the limit of 150.

To get a review, reduce the PR to 150 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to Team to raise the limit.

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: a1d13ea5-ee57-4b57-a02b-9b0294b002af
📥 Commits

Reviewing files that changed from the base of the PR and between 794f239 and c9cfa84.

📒 Files selected for processing: 180
  • .devcontainer/Dockerfile
  • .github/actions/setup/action.yml
  • .github/tsan-libomp.supp
  • .github/workflows/bench.yml
  • .github/workflows/nix.yml
  • .github/workflows/qa-analysis.yml
  • .github/workflows/scripts/check_shape.py
  • .github/workflows/scripts/resolve_cores.py
  • .github/workflows/test.yml
  • AGENTS.md
  • CMakeLists.txt
  • CONTEXT.md
  • README.md
  • benches/LADDER.md
  • benches/README.md
  • benches/conftest.py
  • cmake/compiler_flags/GNU.CXX.cmake
  • cmake/monopropConfig.cmake.in
  • cpp/include/monoprop/Evolution.h
  • cpp/include/monoprop/MPFunctions.h
  • cpp/include/monoprop/MPGraph.h
  • cpp/include/monoprop/MonomialPropagator.h
  • cpp/monoprop/CMakeLists.txt
  • cpp/monoprop/Evolution.cpp
  • cpp/monoprop/MPFunctions.cpp
  • cpp/monoprop/TypeAliases.h
  • cpp/monoprop/algebra/AlgebraCommon.h
  • cpp/monoprop/algebra/MajoranaAlgebra.h
  • cpp/monoprop/detail/CMakeLists.txt
  • cpp/monoprop/detail/EnvConfig.h
  • cpp/monoprop/detail/evolution/CMakeLists.txt
  • cpp/monoprop/detail/evolution/CosineRecompute.h
  • cpp/monoprop/detail/evolution/CosineRecomputeCallbacks.h
  • cpp/monoprop/detail/evolution/LayerReplay.h
  • cpp/monoprop/detail/evolution/layer_build/Common.h
  • cpp/monoprop/detail/evolution/layer_build/Engine.h
  • cpp/monoprop/detail/evolution/layer_build/FusedApply.h
  • cpp/monoprop/detail/evolution/layer_build/QueryWire.h
  • cpp/monoprop/detail/evolution/layer_build/Resolve.h
  • cpp/monoprop/detail/evolution/layer_build/Scan.h
  • cpp/monoprop/detail/graph/MPGraphViews.h
  • cpp/monoprop/detail/graph_encoding/MPGraphEncoding.cpp
  • cpp/monoprop/detail/monomial_propagator/CMakeLists.txt
  • cpp/monoprop/detail/monomial_propagator/MonomialPropagator.inl
  • cpp/monoprop/detail/monomial_propagator/ShardedPropagator.inl
  • cpp/monoprop/detail/mpi/CMakeLists.txt
  • cpp/monoprop/detail/mpi/CheckedCount.h
  • cpp/monoprop/detail/mpi/Comm.h
  • cpp/monoprop/detail/mpi/CpuRelax.h
  • cpp/monoprop/detail/mpi/Exchange.h
  • cpp/monoprop/detail/mpi/HybridComm.h
  • cpp/monoprop/detail/mpi/MPICompat.cpp
  • cpp/monoprop/detail/mpi/MPICompat.h
  • cpp/monoprop/detail/mpi/MPIUtils.h
  • cpp/monoprop/detail/mpi/OperationFailure.h
  • cpp/monoprop/detail/mpi/Pairwise.h
  • cpp/monoprop/detail/mpi/PartitionBarrier.h
  • cpp/monoprop/detail/mpi/Routing.h
  • cpp/monoprop/detail/mpi/ShmComm.h
  • cpp/monoprop/detail/operator/InvertedIndex.h
  • cpp/monoprop/detail/operator/MPOperator.h
  • cpp/monoprop/detail/operator/OperatorIndex.h
  • cpp/monoprop/detail/parallel/CMakeLists.txt
  • cpp/monoprop/detail/parallel/Options.h
  • cpp/monoprop/detail/parallel/ThreadBudget.cpp
  • cpp/monoprop/detail/parallel/ThreadBudget.h
  • cpp/monoprop/detail/parallel/Workshare.h
  • cpp/monoprop/detail/pare/PareGraph.cpp
  • cpp/monoprop/detail/pare/PareGraph.h
  • cpp/monoprop/detail/partition/CMakeLists.txt
  • cpp/monoprop/detail/partition/CpuTopology.cpp
  • cpp/monoprop/detail/partition/CpuTopology.h
  • cpp/monoprop/detail/partition/PartitionGroup.h
  • cpp/monoprop/detail/sharded/CMakeLists.txt
  • cpp/monoprop/detail/sharded/Construction.h
  • cpp/monoprop/detail/sharded/Evaluation.cpp
  • cpp/monoprop/detail/sharded/Evaluation.h
  • cpp/monoprop/detail/sharded/Exchange.cpp
  • cpp/monoprop/detail/sharded/Exchange.h
  • cpp/monoprop/detail/sharded/RootObserver.h
  • cpp/monoprop/detail/sharded/State.h
  • cpp/monoprop/detail/sharded/Team.h
  • cpp/tests/AllocationProbe.cpp
  • cpp/tests/AllocationProbe.h
  • cpp/tests/CMakeLists.txt
  • cpp/tests/KernelTestSupport.h
  • cpp/tests/PropagatorTestAccess.h
  • cpp/tests/README.md
  • cpp/tests/ScanTestSupport.h
  • cpp/tests/SlotBlocks.h
  • cpp/tests/ThreadHarness.h
  • cpp/tests/boost-test.cmake
  • cpp/tests/boostAddTests.cmake
  • cpp/tests/combined_recompute_equivalence.cpp
  • cpp/tests/cpu_topology_tests.cpp
  • cpp/tests/ctor_validation_tests.cpp
  • cpp/tests/env_config_tests.cpp
  • cpp/tests/evolution_detail_tests.cpp
  • cpp/tests/evolved_operator_coefficients_tests.cpp
  • cpp/tests/exact_upper_atol_rescue.cpp
  • cpp/tests/exchange_layout_precondition_tests.cpp
  • cpp/tests/find_package_smoke/CMakeLists.txt
  • cpp/tests/fused_cos_sweep_tests.cpp
  • cpp/tests/hybrid_comm_tests.cpp
  • cpp/tests/link_export_probe/CMakeLists.txt
  • cpp/tests/link_export_probe/link_export_probe.cpp
  • cpp/tests/link_export_probe/sharded_construction_chain.cpp
  • cpp/tests/link_export_probe/sharded_evaluation_chain.cpp
  • cpp/tests/link_export_probe/sharded_state_chain.cpp
  • cpp/tests/majorana_cutoff_tests.cpp
  • cpp/tests/mp_operator_tests.cpp
  • cpp/tests/mpi_alltoallv_tests.cpp
  • cpp/tests/mpi_distributed_layer_equivalence.cpp
  • cpp/tests/mpi_failure_driver.cpp
  • cpp/tests/mpi_fresh_insert_equivalence.cpp
  • cpp/tests/mpi_utils_tests.cpp
  • cpp/tests/openmp_kernel_tests.cpp
  • cpp/tests/openmp_runtime_tests.cpp
  • cpp/tests/openmp_workshare_tests.cpp
  • cpp/tests/operator_index_tests.cpp
  • cpp/tests/pare_graph_tests.cpp
  • cpp/tests/partition_equivalence_tests.cpp
  • cpp/tests/partition_group_clone_tests.cpp
  • cpp/tests/pauli_build_layer_tests.cpp
  • cpp/tests/run_mpi_failure_scenario.cmake
  • cpp/tests/sharded_api_tests.cpp
  • cpp/tests/sharded_construction_tests.cpp
  • cpp/tests/sharded_evaluation_tests.cpp
  • cpp/tests/sharded_exchange_tests.cpp
  • cpp/tests/sharded_root_tests.cpp
  • cpp/tests/sharded_state_tests.cpp
  • cpp/tests/sharded_team_tests.cpp
  • cpp/tests/shm_comm_tests.cpp
  • cpp/tests/sparse_fold_tests.cpp
  • cpp/tests/sparse_query_tests.cpp
  • cpp/tests/sparse_resolve_tests.cpp
  • cpp/tests/term_expectation_values_tests.cpp
  • cpp/tests/unit_tests.cpp
  • docs/content/docs/benchmarks.mdx
  • docs/content/docs/building.mdx
  • docs/content/docs/features/parallelism.mdx
  • docs/content/docs/testing.mdx
  • docs/superpowers/plans/2026-09-18-rank-local-openmp.md
  • docs/superpowers/plans/2026-09-29-sharded-openmp.md
  • docs/superpowers/plans/2026-10-11-open-points.md
  • docs/superpowers/specs/2026-09-18-rank-local-openmp-design.md
  • docs/superpowers/specs/2026-09-29-sharded-openmp-design.md
  • justfile
  • nix/devshell.nix
  • nix/monoprop.nix
  • packages/bench-third-party/majorana_prop/monoprop_hubbard1d_benchmark.py
  • packages/bench-third-party/majorana_prop/run_benchmarks.sh
  • packages/monoprop-bench-tools/src/monoprop_bench_tools/bmf.py
  • packages/monoprop-bench-tools/src/monoprop_bench_tools/preflight.py
  • packages/monoprop-bench-tools/src/monoprop_bench_tools/report.py
  • packages/monoprop-bench-tools/tests/test_bmf.py
  • packages/monoprop-bench-tools/tests/test_preflight.py
  • packages/monoprop-bench-tools/tests/test_report.py
  • prek.toml
  • pyproject.toml
  • sonar-project.properties
  • src/monoprop/bindings/binder.h
  • src/monoprop/bindings/bindings.cpp.in
  • src/monoprop/monomial_propagator.py
  • tests/conftest.py
  • tests/mpi_thread_support_probe.py
  • tests/sharded_openmp_probe.py
  • tests/test_mpi_thread_support.py
  • tests/test_openmp_config.py
  • tests/test_rank_local_openmp_benchmark.py
  • tests/test_sharded_openmp.py
  • tools/benchmark-rank-local-openmp.py
  • tools/index-miniapp/CMakeLists.txt
  • tools/index-miniapp/README.md
  • tools/index-miniapp/main.cpp
  • tools/install-deps.sh
  • tools/packages/apt.txt
  • tools/packages/brew.txt
  • tools/rank-local-openmp-campaign.json
  • tools/rank-local-openmp-workloads.json

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Review on demand using usage pricing
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file python cpp ci tools labels Oct 9, 2026
Record the outcome of S8's final campaign on the final binaries (sources of d359c15): 145 of 250
cells pass all five gates (S7: 102), so strict parity is not demonstrated. Runtime is judged
under option B and memory against fresh baseline samples only, with the page-cache protocol
applied every 25 cells. Numerics agree in every cell. The remaining failures are the accepted MPI
1x1 runtime tail (41 cells), a few larger runtime misses (pare-construct-schr at T = 96 and
2x48, energy-hubb at 2x48, tiny propagate at 2x48), and memory excesses of median 0.56 MiB
(ratio 1.003) against the fresh baseline.

Assisted-by: Pi:claude-opus-5-5
@robertodr

Copy link
Copy Markdown
Member Author

🤖 AI text below 🤖

S8 final parity campaign: results

145 of 250 cells pass all five gates (S7: 102), so strict parity is not demonstrated. Numerics agree with the baseline in all 250 cells. Of the 105 failing cells, 41 fail on the MPI 1x1 runtime tail, which the owner accepted before the campaign, and most of the remaining memory failures are below 1 MiB.

Setup

  • Binaries: built from the sources of d359c15 (MPI-off and MPI builds).

  • Harness: the frozen driver, workloads and comparator, unchanged; the runner is derived from S7's.

  • Runtime gate: option B, i.e. Task 1's archived five baseline samples, plus fresh baseline repeats when a cell repeats.

  • Memory gates: against fresh baseline samples only. The archived samples were taken in an unknown page-cache state, which moves RSS by up to about 10 MiB.

  • Page-cache protocol, every 25 cells:

    1. hash the binaries;
    2. evict both arms' shared objects from the page cache;
    3. one warm-up launch per arm and build.

    Arms alternate within each pair.

  • Verdict: strict, all five median ratios ≤ 1.00, with no known-deviation exemptions.

Run

Phase Time used Cap
Setup ~6 min 1.5 h
Collection 15.94 h 20 h
Final comparisons 2.14 h 4 h
  • No stops and no failed launches.
  • 109 cells were repeated to ten pairs.
  • The combined report agrees with the runner's verdict for every cell.

Results by geometry

Geometry S8 pass S7 pass
MPI-off 1x96 45 / 62 14
MPI 1x96 44 / 62 26
MPI 2x48 39 / 62 39
MPI 1x1 5 / 52 11
splitmix / linear multi-rank 12 / 12 12

Against S7, 62 cells newly pass and 19 newly fail. Most new failures are memory excesses of 0.1–1.5% against the fresh baseline; S7 judged memory against the archived samples, which ran higher.

What still fails

  • Runtime (57 cells):
    • 41 are MPI 1x1, the accepted tail (41 of the 52 1x1 cells). That tail is why only 5 of the 1x1 cells pass, against 11 in S7.
    • The other 16:
      • reference-pared pare-construct-schr at T = 96 and 2x48: 1.28–1.40;
      • reference energy-hubb at 2x48: 1.27;
      • four tiny build-graph/propagate cells at 2x48: 1.06–1.28;
      • a handful of reference energy/gradient-hubb and pare-construct-heis cells: 1.02–1.08.
  • Memory (80 cells on operation peak, 66 on construction peak):
    • The median failing ratio is 1.003; the median excess is 0.56 MiB, the 90th percentile 7 MiB, the maximum 30 MiB.
    • The worst ratios are reference gradient-hubb at 2x48, about +1.5% on construction peak, and the large gradient-hubb cells at +0.1–0.5%.
  • Large cells: 25 of 30 pass. The five failures are memory only, by at most +0.5%.

The outcome is recorded in the sharded OpenMP plan (5ce60df). Next: discuss an optimization round on the largest remaining misses.

Roberto Di Remigio Eikås added 3 commits October 10, 2026 16:52
… at T > 1

Round 5 of S8's optimization, on the memory gates' remaining misses. Every change is bitwise
neutral (differential against the cutover build: 27/27 MPI-off, 136/136 MPI).

- Seed each shard in place on the heap. seed_operator() fills the operator where it lives and
  ShardState(bool) builds an empty one, so no MPOperator<N> (about 112 N bytes, its inverted
  index's columns inline) passes through a worker's frame. Worker stacks stay resident for the
  pool's life; at T = 96 they held 9 pages each instead of the baseline threads' 3, now 4.
- Construction's physical rounds unmap their staging when a construction ends; the replay
  round keeps its staging. An outgrown staging block is unmapped before the larger one is
  mapped, so the two are never resident together.
- Partner staging is no longer widened to a step's largest block: a block larger than the run
  is read in place from the partner's publication. The run size is unchanged.
- Per-step publication and staging buffers grow by capacity (grow_to()), not from their
  current size, which reallocated thousands of times per evaluation at T = 96 and fragmented
  the owners' malloc arenas.

Targeted re-measure against fresh baseline samples (3 pairs per cell): every measured tiny
Pauli cell at T = 96 and the Hubbard gradient cells now pass their memory gates; Hubbard and
Heisenberg energy at 1x96 still exceed the baseline by about 3.5 MiB. No runtime regression
found. New tests: seeding stack use, construction staging release, staging sizing with
in-place blocks, buffer growth by capacity.

Assisted-by: Pi:claude-opus-5-5
Record round 5 in the sharded OpenMP plan: the owner's choice (the memory misses, options D and
B), the diagnostics (worker stacks held by seeding's on-stack operators, Hubbard partner
staging and incremental buffer growth, construction staging kept through evaluation at 2x48),
the four kept bitwise-neutral changes of 4fdddbc and their verification, and the targeted
re-measure: the measured tiny Pauli cells at T = 96 and the Hubbard gradient cells now pass
their memory gates, energy at 1x96 still does not. The final campaign's verdict stands until a
re-run on the round-5 binaries, which awaits the owner.

Assisted-by: Pi:claude-opus-5-5
Bring main's 18 commits since 90d5717 into the branch: dependency and CI bumps, the 0.9.2
version, Pauli skip_validation, upstream nanobind 3.1.0 with per-interpreter Nix packages,
read individual evolved coefficients (#337) and the N-independent emit and fold (#383).

Ported onto the sharded runtime:
- evolved_operator_coefficients() probes every shard's store against its contracted block;
  shards hold disjoint rows, so at most one finds a key (rank-local under MPI, as on main;
  the Heisenberg identity is the rank-level core term). Main's C++ suite runs at the launch's
  T and again at T = 2 and 4 (evolved_coefficients_env_t2/_t4) instead of comparing partition
  counts in one process.
- The scan's positions emit runs in the range kernel when there is one flat owner (P = T = 1),
  main's single-rank condition; the prefetch-ahead loop binds the thread_local nz once.
- scale_cos_lazy keeps its fold-block ranges, each now running for_each_fold_word() over its
  own words (the posting-driven fold where the columns are sparse enough); cos_count_lazy
  counts through the same fold.
- Nix: main's per-interpreter checks and CI with the hwloc and pkg-config inputs still gone.

Numerics are unchanged: the differential against the S8 cutover build is 27/27 MPI-off and
136/136 MPI rank documents.

Assisted-by: Pi:claude-opus-5-5
@github-actions

Copy link
Copy Markdown

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

Roberto Di Remigio Eikås added 7 commits October 11, 2026 19:51
…small cosine calls

The merge of main brought the posting-driven fold (#383) into every owner's per-call cosine
scaling. Measured against round 5's binaries, that cost the sharded runtime at T > 1 and T = 1:

- Each thread held a second 8 KiB zero block next to the word sweep's block, acquired before
  a fold knew whether any block took the posting path: about +1.4 MiB of thread heap per
  process at T = 96 during evaluation, enough to fail six memory gates round 5 passed.
- for_each_fold_word()'s setup (cursors, a lower_bound per column, posting counts) dominated
  small folds: a T = 1 tiny energy call went from 12.4 to 17.8 µs.

Now the word sweep and the posting fold share one block per thread (SharedFoldBlock): the
posting fold re-zeroes it only after a word sweep dirtied it. Per-call cosine folds of one
block take the direct word sweep (for_each_cos_fold_word()); longer folds keep main's kernel.
Both are bitwise neutral (differential against the S8 cutover build: 27/27, 136/136).

Re-measured against fresh baseline samples: five of the six cells pass again (the sixth at
1.0005), Hubbard build-graph keeps main's −61 to −72 MiB, and the tiny T = 1 energy call is
back to 13.4 µs warm and 28.8 µs first (runtime ratio 1.09, campaign 1.16).

Assisted-by: Pi:claude-opus-5-5
Record in the sharded OpenMP plan the merge of origin/main into the branch (94cbf92): main's
scope, how evolved_operator_coefficients() and the N-independent emit and posting-driven fold
were ported onto the shards, and the verification. Record step 6, the owner-approved
measurement of the merged binaries against round 5: main's gains (Hubbard build-graph −60 to
−72 MiB, Hubbard propagate 1x1 runtime 0.94), the per-thread fold block and small-fold setup
it cost, the two bitwise-neutral fixes of 1a4573d, the TSan scenarios that time out on both
sides of the change, and the re-measure. The campaign verdict stands until a re-run on the
current binaries.

Assisted-by: Pi:claude-opus-5-5
…penMP refactor

Bring in main's two test commits since 67e7c4c: pytest-cases replaced with plain pytest (#404)
and tests/cases.py renamed to tests/reference_data.py (#407).

- tests/conftest.py: main's reference-data imports beside the branch's guarded mpi4py import,
  so an MPI-off build still never initializes MPI.
- prek.toml: the name-tests-test exclusion keeps both reference-data modules and the branch's
  two probe scripts.
- tests/sharded_openmp_probe.py loads its problems from tests.reference_data.

No library source changes, so the binaries are unchanged. The full Python matrix passes with
the merged tests (pytest 916 / 892 / 892 MPI-off, 1003 / 955 / 955 MPI, mpiexec -n 2 785).

Assisted-by: Pi:claude-opus-5-5
…s kept for later

Add a companion to the sharded OpenMP plan listing what is still open after S8, round 5, the
merges of main and step 6: the remaining memory and runtime misses with their causes and
possible remedies, the opportunities main's #383 opens (positions emit with several owners,
sharded self-resolve prefetch, the posting fold on mid-size shards), and the items recorded
for later (the post-landing numerical-performance policy, S5's flagged optimizations and their
status, pending qualification). Add two items to evaluate: explicit OpenMP simd directives and
OpenMP tasking, each with where it could help, the constraints it must respect (bitwise
numerics, FUNNELED MPI, owner-computes memory, failure handling) and how to measure it.

Assisted-by: Pi:claude-opus-5-5
`stack_bytes_touched` used Linux's `MADV_NOHUGEPAGE` and passed an
`unsigned char *` residency vector to `mincore`, which macOS declares with
`char *`, so every macOS job failed to compile `sharded_state_tests.cpp`.
Skip the huge-page opt-out where the flag does not exist and type the
vector per platform.

Assisted-by: Pi:claude-opus-5-5
GCC's default `-ffp-contract=fast` fuses multiplies and adds across
statements, so two code paths computing the same expression can round
differently. With g++-14 this broke the bitwise cross-path checks of five
C++ test cases even at one thread. `-ffp-contract=on` contracts within one
expression only, as Clang does by default; with it the full suite passes on
g++-14 and g++-15.

Assisted-by: Pi:claude-opus-5-5
`_child_env` strips every launcher variable, including the justfile's Open
MPI oversubscription settings, so the 3- and 4-rank probes failed with
`prte-rmaps-base:alloc-error` on 2-slot runners. Restore both spellings in
`_run_ranks`.

Assisted-by: Pi:claude-opus-5-5
@robertodr
robertodr marked this pull request as ready for review October 16, 2026 11:57
set(
monoprop_CXX_FLAGS
"-Wall -Wno-unknown-pragmas -Wno-sign-compare -Woverloaded-virtual -Wwrite-strings -Wextra -Wconversion -Wnon-virtual-dtor -Wcast-align -Wunused-parameter -fdiagnostics-color=always -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"
"-Wall -Wno-unknown-pragmas -Wno-sign-compare -Woverloaded-virtual -Wwrite-strings -Wextra -Wconversion -Wnon-virtual-dtor -Wcast-align -Wunused-parameter -fdiagnostics-color=always -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -ffp-contract=on"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not quite sure I agree with this one.

Roberto Di Remigio Eikås added 3 commits October 16, 2026 17:32
- test-cpp-tsan now also selects every `openmp_*` case, the multi-shard
  coefficient launches and the threaded lazy-fold checks, which its
  comment already claimed to cover (142 -> 215 cases).
- test-py-asan runs at T = 1 like every other ordinary suite, instead of
  at the runner's core count.
- The sharded MPI launches, the MPI failure scenarios and the link probe
  allow oversubscription in both Open MPI spellings, so a direct
  `ctest -L mpi` fits 3- and 4-rank launches onto smaller hosts.
- test_wrong_thread_entry_fails_fast fails, rather than skips, when the
  probe dies before reporting.
- Drop the Sonar suppressions for the deleted PartitionBarrier.h and
  HybridComm.h.

Assisted-by: Pi:claude-opus-5-5
…paths

- update_initial_operator rejects a Heisenberg term the operator lacks
  before the epoch, the identity or any shard changes
  (MPOperator::require_updatable_terms), so the propagator stays usable
  as parallelism.mdx promises, and a mistyped term no longer aborts an
  MPI job.
- A construction team failure on a multi-rank communicator always goes
  to mpi::operation_failed, including a pre-mutation frame allocation,
  so peers are never left blocked in the operation's rounds.
- mutation_failed_ makes no MPI call on one rank, and above one rank
  stops a non-initializing thread locally instead of calling MPI_Abort
  from it.
- Rename Router::partitions() to shards_per_rank() and the new kName
  constants to snake_case, per AGENTS.md; retire stale "partition"
  comments in the library, CI script and third-party bench scripts.
- Document the shard-aggregating accessors, fix the EvaluationObserver
  doc block and a broken \throws wrap, drop includes the .inl no longer
  uses, and correct AGENTS.md on what build_layer is kept for.

Assisted-by: Pi:claude-opus-5-5
…the sharded OpenMP refactor

Bring in main since 9682c6b: term_expectation_values() (#393), the ruff 0.17 and notebook lint
fixes (#416, #417) and dependency bumps.

- term_expectation_values() is ported to the sharded root (ShardedPropagator.inl). Each shard's
  state is scattered by its owner (RootWork::adjoint_seed) and swept back through its layers,
  newest first, at the negated angles via replay_shards() over the new
  MPGraph::reverse_replay_view() -- the sharded form of ev_op_gradient(), which stays as the
  one-store entry. Every key's value and found flag are gathered across shards and ranks.
- The Heisenberg miss check (OperatorTermNotFound) runs before mutation: the lookup reads the
  frozen stores and the flag allreduce is collective over replicated arguments, so the miss stays
  a recoverable argument error, as on main.
- heisenberg_gates_absorbed_ is set by a Heisenberg propagate() and an in-place
  contract_partially() (contract_blocks_), and is copied with the propagator.
- PartitionGroup.h and partition_group_clone_tests.cpp stay deleted: main's poison-rethrow fix
  belongs to the removed partition runtime.
- term_expectation_values_tests.cpp drops the partition arguments, prefixes its cases and reruns
  at T = 2 and 4 (term_expectation_values_env_t*), against expectation_value() at the same T.
- report.py: main's parenthesised header string with the branch's exactness sentence.
- The two plan documents are reformatted for ruff 0.17's Markdown code blocks.

MPI-off Release: ctest 515/515, pytest 904 passed; values are identical at T = 1, 2, 4 and 7.

Assisted-by: Pi:claude-opus-5-5

This branch has not been deployed

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

Labels

ci cpp dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation python tools

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant