docs(benches): 🏗️ benchmarking ladder for sensitive PRs - #317
Conversation
|
Docs preview: https://pr-317.monoprop-docs.pages.dev |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #317 +/- ##
=======================================
Coverage ? 97.70%
=======================================
Files ? 14
Lines ? 742
Branches ? 98
=======================================
Hits ? 725
Misses ? 12
Partials ? 5
Flags with carried forward coverage won't be shown. Click here to find out more. |
da9aafc to
bb1254b
Compare
Addresses the four review comments on #317. L1 gains two `gradient` rows at ~20M terms — one against the exact graph, one against a plan pared at `1e-10` — so a change to the graph functionals shows on the pair. `--pare-threshold` is a new pytest option threaded into `expectation_value_functional` and its gradient twin for both the random and the fixed-model benchmarks; unset reproduces today's behaviour exactly. L3 and L4 no longer prescribe a shape. `N`, `R` and `P` are the caller's, the L3 table gives absolute ballparks like every other group instead of ratios against L2, and declaring the shape is now mandatory rather than advisory: `conftest.py` refuses a multi-rank session with `monoprop_PARTITIONS` unset, since the engine's `ranks == 1 ? cores : 1` default would otherwise measure one partition per rank at a plausible wall time. The shape is recorded on every run — `partitions_env` unconditionally now, so "unset" and "not recorded" stop rendering identically in `REPORT.md`. The model size knobs become usable inputs. `--pauli-num-qubits` other than the 127 that `HEAVY_HEX_TOPOLOGY` couples, `--hubbard-observable-site` off its lattice, and any mode count above the extension's `MAX_NUM_MODES` all raise where they previously built a silently different model. `LADDER.md` documents the knobs, their ranges, and that `--obs-terms` is an upper bound rather than an exact count. `bench-mpi` forwards `monoprop_PARTITIONS` and `monoprop_NUM_THREADS` through `mpiexec`, which its own doc comment already assumed. AGENTS.md is cut to two thirds, its truncated opening sentence repaired, and told to run `prek` before pushing. Assisted-by: claude-code:claude-opus-5
Addresses the four review comments on #317. L1 gains two `gradient` rows at ~20M terms — one against the exact graph, one against a plan pared at `1e-10` — so a change to the graph functionals shows on the pair. `--pare-threshold` is a new pytest option threaded into `expectation_value_functional` and its gradient twin for both the random and the fixed-model benchmarks; unset reproduces today's behaviour exactly. L3 and L4 no longer prescribe a shape. `N`, `R` and `P` are the caller's, the L3 table gives absolute ballparks like every other group instead of ratios against L2, and declaring the shape is now mandatory rather than advisory: `conftest.py` refuses a multi-rank session with `monoprop_PARTITIONS` unset, since the engine's `ranks == 1 ? cores : 1` default would otherwise measure one partition per rank at a plausible wall time. The shape is recorded on every run — `partitions_env` unconditionally now, so "unset" and "not recorded" stop rendering identically in `REPORT.md`. The model size knobs become usable inputs. `--pauli-num-qubits` other than the 127 that `HEAVY_HEX_TOPOLOGY` couples, `--hubbard-observable-site` off its lattice, and any mode count above the extension's `MAX_NUM_MODES` all raise where they previously built a silently different model. `LADDER.md` documents the knobs, their ranges, and that `--obs-terms` is an upper bound rather than an exact count. `bench-mpi` forwards `monoprop_PARTITIONS` and `monoprop_NUM_THREADS` through `mpiexec`, which its own doc comment already assumed. AGENTS.md is cut to two thirds, its truncated opening sentence repaired, and told to run `prek` before pushing. Assisted-by: claude-code:claude-opus-5
52824a6 to
4836a27
Compare
There was a problem hiding this comment.
Pull request overview
Adds a “benchmarking ladder” guide and tightens the benchmark harness so sensitive performance work can be reproduced reliably (especially under MPI) while keeping default/CI benchmark flows unchanged.
Changes:
- Document a four-level benchmarking ladder (
L1–L4) with exactpytestselectors/flags and required shape declaration. - Add a
--pare-thresholdbenchmark sizing option and thread it into energy/gradient functionals for both random and fixed-model benches. - Make multi-rank benchmark sessions require an explicit
monoprop_PARTITIONS, forward shape/thread env vars throughjust bench-mpi, and add input validation for benchmark model sizing traps.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/monoprop-bench-tools/src/monoprop_bench_tools/models.py | Adds MAX_NUM_MODES-based validation and rejects invalid benchmark model sizes/indices. |
| justfile | Ensures MPI bench runs forward monoprop_PARTITIONS / monoprop_NUM_THREADS to ranks and updates usage docs. |
| docs/content/docs/benchmarks.mdx | Documents mandatory shape declaration for multi-rank runs and links to the ladder. |
| benches/README.md | Points readers to the ladder and notes newly-enforced sizing/shape constraints. |
| benches/LADDER.md | Introduces the benchmarking ladder playbook and calibrated configurations. |
| benches/conftest.py | Adds --pare-threshold, records partitions_env unconditionally, and errors early if multi-rank runs omit monoprop_PARTITIONS. |
| benches/bench_random.py | Passes pare_threshold into random energy/gradient functionals. |
| benches/bench_models.py | Passes pare_threshold into fixed-model energy/gradient functionals. |
| AGENTS.md | Condenses and corrects the repo agent/developer guidance (commands, layout, rules). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
robertodr
left a comment
There was a problem hiding this comment.
split the changes to AGENTS.md to a separate PR.
Addresses the four review comments on #317. L1 gains two `gradient` rows at ~20M terms — one against the exact graph, one against a plan pared at `1e-10` — so a change to the graph functionals shows on the pair. `--pare-threshold` is a new pytest option threaded into `expectation_value_functional` and its gradient twin for both the random and the fixed-model benchmarks; unset reproduces today's behaviour exactly. L3 and L4 no longer prescribe a shape. `N`, `R` and `P` are the caller's, the L3 table gives absolute ballparks like every other group instead of ratios against L2, and declaring the shape is now mandatory rather than advisory: `conftest.py` refuses a multi-rank session with `monoprop_PARTITIONS` unset, since the engine's `ranks == 1 ? cores : 1` default would otherwise measure one partition per rank at a plausible wall time. The shape is recorded on every run — `partitions_env` unconditionally now, so "unset" and "not recorded" stop rendering identically in `REPORT.md`. The model size knobs become usable inputs. `--pauli-num-qubits` other than the 127 that `HEAVY_HEX_TOPOLOGY` couples, `--hubbard-observable-site` off its lattice, and any mode count above the extension's `MAX_NUM_MODES` all raise where they previously built a silently different model. `LADDER.md` documents the knobs, their ranges, and that `--obs-terms` is an upper bound rather than an exact count. `bench-mpi` forwards `monoprop_PARTITIONS` and `monoprop_NUM_THREADS` through `mpiexec`, which its own doc comment already assumed. AGENTS.md is cut to two thirds, its truncated opening sentence repaired, and told to run `prek` before pushing. Assisted-by: claude-code:claude-opus-5
Addresses the four review comments on #317. L1 gains two `gradient` rows at ~20M terms — one against the exact graph, one against a plan pared at `1e-10` — so a change to the graph functionals shows on the pair. `--pare-threshold` is a new pytest option threaded into `expectation_value_functional` and its gradient twin for both the random and the fixed-model benchmarks; unset reproduces today's behaviour exactly. L3 and L4 no longer prescribe a shape. `N`, `R` and `P` are the caller's, the L3 table gives absolute ballparks like every other group instead of ratios against L2, and declaring the shape is now mandatory rather than advisory: `conftest.py` refuses a multi-rank session with `monoprop_PARTITIONS` unset, since the engine's `ranks == 1 ? cores : 1` default would otherwise measure one partition per rank at a plausible wall time. The shape is recorded on every run — `partitions_env` unconditionally now, so "unset" and "not recorded" stop rendering identically in `REPORT.md`. The model size knobs become usable inputs. `--pauli-num-qubits` other than the 127 that `HEAVY_HEX_TOPOLOGY` couples, `--hubbard-observable-site` off its lattice, and any mode count above the extension's `MAX_NUM_MODES` all raise where they previously built a silently different model. `LADDER.md` documents the knobs, their ranges, and that `--obs-terms` is an upper bound rather than an exact count. `bench-mpi` forwards `monoprop_PARTITIONS` and `monoprop_NUM_THREADS` through `mpiexec`, which its own doc comment already assumed. AGENTS.md is cut to two thirds, its truncated opening sentence repaired, and told to run `prek` before pushing. Assisted-by: claude-code:claude-opus-5
07456b1 to
fd894ba
Compare
Addresses the four review comments on #317. L1 gains two `gradient` rows at ~20M terms — one against the exact graph, one against a plan pared at `1e-10` — so a change to the graph functionals shows on the pair. `--pare-threshold` is a new pytest option threaded into `expectation_value_functional` and its gradient twin for both the random and the fixed-model benchmarks; unset reproduces today's behaviour exactly. L3 and L4 no longer prescribe a shape. `N`, `R` and `P` are the caller's, the L3 table gives absolute ballparks like every other group instead of ratios against L2, and declaring the shape is now mandatory rather than advisory: `conftest.py` refuses a multi-rank session with `monoprop_PARTITIONS` unset, since the engine's `ranks == 1 ? cores : 1` default would otherwise measure one partition per rank at a plausible wall time. The shape is recorded on every run — `partitions_env` unconditionally now, so "unset" and "not recorded" stop rendering identically in `REPORT.md`. The model size knobs become usable inputs. `--pauli-num-qubits` other than the 127 that `HEAVY_HEX_TOPOLOGY` couples, `--hubbard-observable-site` off its lattice, and any mode count above the extension's `MAX_NUM_MODES` all raise where they previously built a silently different model. `LADDER.md` documents the knobs, their ranges, and that `--obs-terms` is an upper bound rather than an exact count. `bench-mpi` forwards `monoprop_PARTITIONS` and `monoprop_NUM_THREADS` through `mpiexec`, which its own doc comment already assumed. AGENTS.md is cut to two thirds, its truncated opening sentence repaired, and told to run `prek` before pushing. Assisted-by: claude-code:claude-opus-5
40008fb to
e161fa1
Compare
Addresses the four review comments on #317. L1 gains two `gradient` rows at ~20M terms — one against the exact graph, one against a plan pared at `1e-10` — so a change to the graph functionals shows on the pair. `--pare-threshold` is a new pytest option threaded into `expectation_value_functional` and its gradient twin for both the random and the fixed-model benchmarks; unset reproduces today's behaviour exactly. L3 and L4 no longer prescribe a shape. `N`, `R` and `P` are the caller's, the L3 table gives absolute ballparks like every other group instead of ratios against L2, and declaring the shape is now mandatory rather than advisory: `conftest.py` refuses a multi-rank session with `monoprop_PARTITIONS` unset, since the engine's `ranks == 1 ? cores : 1` default would otherwise measure one partition per rank at a plausible wall time. The shape is recorded on every run — `partitions_env` unconditionally now, so "unset" and "not recorded" stop rendering identically in `REPORT.md`. The model size knobs become usable inputs. `--pauli-num-qubits` other than the 127 that `HEAVY_HEX_TOPOLOGY` couples, `--hubbard-observable-site` off its lattice, and any mode count above the extension's `MAX_NUM_MODES` all raise where they previously built a silently different model. `LADDER.md` documents the knobs, their ranges, and that `--obs-terms` is an upper bound rather than an exact count. `bench-mpi` forwards `monoprop_PARTITIONS` and `monoprop_NUM_THREADS` through `mpiexec`, which its own doc comment already assumed. AGENTS.md is cut to two thirds, its truncated opening sentence repaired, and told to run `prek` before pushing. Assisted-by: claude-code:claude-opus-5
e3588ec to
6f16b9c
Compare
Replaces the rung runner with the document it was wrapping. `benches/LADDER.md` names four groups of benchmark configurations at the sizes the library is used at — L1 one thread at ~10M terms, L2 one node at ~1B, L3 the same problems on four nodes, L4 the strong and weak scaling ladders — each as the exact pytest flags and `-k` selector that produce it, run through the ordinary `just bench` route. No CLI, no TOML schema, no gate, and nothing to recalibrate but a term count in a table. The sizes, term counts and costs are the 2026-08-27 campaign's; time and memory are rounded to a figure that sizes a job rather than quoted as a baseline. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Updated the benchmarking ladder documentation to enhance clarity and accuracy regarding benchmark configurations and their usage. Signed-off-by: Aaron Miller <61472721+diagonal-hamiltonian@users.noreply.github.com>
Updated the benchmarks documentation to include a new section on the benchmarking ladder and improved clarity on scaling and memory usage. Signed-off-by: Aaron Miller <61472721+diagonal-hamiltonian@users.noreply.github.com>
Addresses the four review comments on #317. L1 gains two `gradient` rows at ~20M terms — one against the exact graph, one against a plan pared at `1e-10` — so a change to the graph functionals shows on the pair. `--pare-threshold` is a new pytest option threaded into `expectation_value_functional` and its gradient twin for both the random and the fixed-model benchmarks; unset reproduces today's behaviour exactly. L3 and L4 no longer prescribe a shape. `N`, `R` and `P` are the caller's, the L3 table gives absolute ballparks like every other group instead of ratios against L2, and declaring the shape is now mandatory rather than advisory: `conftest.py` refuses a multi-rank session with `monoprop_PARTITIONS` unset, since the engine's `ranks == 1 ? cores : 1` default would otherwise measure one partition per rank at a plausible wall time. The shape is recorded on every run — `partitions_env` unconditionally now, so "unset" and "not recorded" stop rendering identically in `REPORT.md`. The model size knobs become usable inputs. `--pauli-num-qubits` other than the 127 that `HEAVY_HEX_TOPOLOGY` couples, `--hubbard-observable-site` off its lattice, and any mode count above the extension's `MAX_NUM_MODES` all raise where they previously built a silently different model. `LADDER.md` documents the knobs, their ranges, and that `--obs-terms` is an upper bound rather than an exact count. `bench-mpi` forwards `monoprop_PARTITIONS` and `monoprop_NUM_THREADS` through `mpiexec`, which its own doc comment already assumed. AGENTS.md is cut to two thirds, its truncated opening sentence repaired, and told to run `prek` before pushing. Assisted-by: claude-code:claude-opus-5
Updated the LADDER.md file to improve clarity and structure of benchmarking information. Signed-off-by: Aaron Miller <61472721+diagonal-hamiltonian@users.noreply.github.com>
…and gradient Selecting the three operations in one process built the operator twice: the timed build_graph made its own, then the session built_graph fixture made a second. build_graph now publishes the graph it timed, and energy and gradient evaluate that one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…mitian A product of k Majoranas is Hermitian with real coefficients only for k % 4 in (0, 1), so k=2, 3, 6, 7 reached the extension and surfaced as "Non-Hermitian coeffs detected" from the propagator's constructor, naming neither the option nor the rule. Verified over k=2..11. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nergy from gradient The Schrödinger rows sat at 18.8M terms because --obs-terms, which sizes the Heisenberg rows, is flat in a picture that evolves the state. Cutoff and mode count are cliffs -- 7 and 180 both fail to fit a 242 GiB node where 6 and 160 cost 3.6 and 5.6 GiB -- so the rung is set by the gate count: 4000 generators gives 97,631,757 terms in 16.2 GiB. L2 and L3 now carry energy and gradient as separate rows; gradient is 3.4x energy in the Heisenberg picture and 4.9x in the Schrödinger one, which one combined cell hid. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d_graph The rung was set from a build_graph sweep, which is the wrong operation to size it by: at 6200 generators build_graph peaks at 102.7 GiB and gradient in the same process peaks at 143.1 GiB. Re-measured whole-row peaks and took 5800 generators (597,445,055 terms, 99.7 GiB), the largest that fits the ~110 GiB budget the Heisenberg rows sit at. Records both cliffs found on the way (cutoff 7 and 180 modes each SIGKILL a 242 GiB node where their predecessors cost 3.6 and 5.6 GiB), the generator axis from 1000 to 8000, and why the last operation in a shared process reads highest: it starts at its predecessor's high-water mark, so only 28.8 GiB of gradient's 143.1 is its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
L2 -> L3 moved two things at once: it introduced multiple processes and it introduced the network, so anything that appeared at L3 was attributable to neither. L2 splits into L2a (one rank, partitions = threads = cores, which is the old L2 unchanged) and L2b (one node at R ranks x P partitions, R x P = cores, which is L3's shape at N = 1). Each edge of the ladder now moves one thing: partitions, processes, the network, then node count. L2b is measured at N=1, R=8, P=16 on a 128-core node, two reps with the cell order flipped. Term counts reproduce L2a's and L3's exactly, which is the geometry-independence check the rung relies on. 8 x 16 is stated as a choice, not derived as an optimum: it is one rank per NUMA domain on this machine, and it is L3's shape, so L2b -> L3 differs only in the node count. The section says as much rather than implying a sweep. Two findings worth the reader's attention, both recorded next to the table: - The Heisenberg rows cost 3.6-8.1 GiB per rank beyond their share of the operator, the Schrodinger rows 0.2-0.6, on one shape and one binary. The flag sets differ in the observable, 14.75M terms against 200k. The raw observable is well under a GiB, so something indexed by it scales with it; that is not attributed further here. - Consequently L2b, not L2a, is the memory-worst rung for the Heisenberg rows, because the per-rank cost is paid R times with no extra nodes to spread it over. An earlier draft of this change claimed a row fitting L2a fits L2b, and the measurement refutes it. The GiB/node column is a sum over ranks, as L3's is; reading it against L2a's single-process peak would be a sum-against-max comparison, so the table says so. Also: a new trap, P above the cores a rank can see, which is what carrying L2a's monoprop_PARTITIONS into an L2b launch does; and the multi-rank skeleton now names the interpreter directly, because uv run re-resolves the environment in every rank. Assisted-by: ClaudeCode:claude-opus-5
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Aaron Miller <61472721+diagonal-hamiltonian@users.noreply.github.com>
…e limit The autofix commit ran the line to 127 columns and closed it on "out of range now raises", which the ValueError three lines down already states. Dropping that clause keeps what the comment is for -- the site is a lattice position, so shrinking the lattice without scaling it moves the light cone -- and fits 120. Assisted-by: ClaudeCode:claude-opus-5
Assisted-by: ClaudeCode:claude-opus-5
6f16b9c to
b470584
Compare
|



🤖 AI text below 🤖
benches/LADDER.md— the benchmarking ladder for sensitive PRs — is five groups of benchmark configurations at the sizes the library is actually used at. Each row gives the exact pytest flags and the-kselector that produce it, so a group is a handful of ordinaryjust benchinvocations. Nothing runs these, and no benchmark gates a PR.just bench L1-hubbard-branch --hubbard-cutoff=10 --hubbard-lower-atol=4.2e-05 \ -k "test_model_propagate and hubbard"propagateat ~10M terms; randomgradientat ~20M, paired with and without--pare-thresholdN=1,R × P= coresRandP, yourNN> 1 isolates the networkR,P;N= 1…64propagateon one size sequence; both ladders built from itWhat the review asked for
Gradient rows at 20M terms, with and without
pare_threshold=1e-10.--pare-thresholddid not exist; it is now a pytest option threaded intoexpectation_value_functionalandexpectation_value_and_gradient_functionalfor both the random and the fixed-model benchmarks. Unset reproduces today's behaviour exactly, so the Bencher series are unbroken. Measured at 1 rank / 1 partition / 1 thread (_core.somd5b201ec4),--obs-terms=295000landing 19,902,244 terms:gradientgradient,--pare-threshold=1e-10Hubbard is not a candidate — 29 successive
build_graphcalls retain 29 layer-sets andskip_if_graph_will_not_fitskips it — so the gradient rows use the random problem, whose size is directly dialable. Proved the knob reaches the engine rather than being swallowed:1e-10leaves the energy bit-identical (the point of a retention cutoff),1e10moves it 0.8049 → 0.8307.L3's ratios removed. The "against L2" column is gone; L3 reads like L2 and L1 — flags,
-k, terms,~s,~GiB/node— with the shape those cells were measured at stated underneath (N=4,R=8,P=16, medians of two reps) rather than as a prescription.L3 and L4 generalised, and the shape made mandatory.
N,RandPare the caller's throughout, andbenches/conftest.pynow refuses to start a session of more than one rank withmonoprop_PARTITIONSunset. The engine's own default isranks == 1 ? cores : 1, so an unset knob measured one partition per rank at a plausible wall time — a trap the document used to merely warn about. Every rank raises after the collective_nodes(), so nothing is left in a collective. One-rank runs —just bench,bench-smoke,bench-ci, every CI workflow — are untouched.The shape recorded where the benchmarks record.
metaalready carriedranks,nodes,ranks_per_nodeandmonoprop_threads, with report columns for each — butpartitions_envwas written only when the env var was set, so an undeclared partition count and an unrecorded one both rendered—. It is now recorded unconditionally,"unset"when absent.Model sizes are inputs now
--pauli-num-qubitsaccepted any value whileHEAVY_HEX_TOPOLOGYstayed the fixed 127-qubit IBM Eagle map — silently a different model, or an index past the operator--pauli-lower-atolfor sizing--hubbard-observable-siteabove--hubbard-num-sites— a documented "trap" that produced a wrong observableMAX_NUM_MODESfailed deep inside the extension--pauli-observable-qubitoutside the registerLADDER.mdgains a Model size knobs table covering these plus--obs-terms, which is an upper bound rather than an exact count: monomials are drawn independently and duplicates collapse, by aboutobs_terms / 2·C(2·num_modes, gen_length). Checked against a direct count at 142 modes — 0.03/0.19/0.37% measured at 200k/1M/2M draws against 0.04/0.19/0.38% predicted — giving 0.06% at L1's 295k and 2.8% at L2's 14.75M. Deterministic for a fixed--seed, so the calibration reproduces.Also
just bench-mpinever forwardedmonoprop_PARTITIONSormonoprop_NUM_THREADSthroughmpiexec -x, though its own doc comment showedmonoprop_NUM_THREADS=2 just bench-mpi …. Both are forwarded now, guarded so an unset one is not an mpiexec error.Traps, each from a failure it caught during calibration
srun --cpu-bind=coreswith no--cpus-per-taskon thesrunconfines each task to one core. Measured in one allocation: no flags → 128,--cpus-per-task=128→ 128,--cpu-bind=coresalone → 1. ~100x, with nothing in the timing to say so.srun. Under it, one process per rank races to write the same file. The two JSON artifacts are safe: only rank 0 writes them.HighWaterMarkresetsVmHWMper benchmark, so both windows contain the same resident operator.--cpu-bind=nonecost 1.45x;nproclies inside a job.monoprop_PARTITIONSand observable-site traps are no longer traps — both now raise.The L2a / L2b split
L2→L3moved two things at once — multiple processes and the network — so anything thatappeared at L3 was attributable to neither. L2 splits: L2a is the old L2 unchanged (one rank,
partitions = threads = cores), and L2b is L3's shape atN=1. Each edge of the ladder nowmoves one thing: partitions, processes, the network, then node count.
L2b is measured at
N=1,R=8,P=16 on a 128-core node, two reps with the cell order flipped(job 1862701,
_core.somd5b201ec44). Term counts reproduce L2a's and L3's exactly —1,001,661,534 / 985,970,588 / 948,937,993 / 597,445,055 — which is the geometry-independence check
the rung relies on.
8 × 16is stated as a choice, not derived as an optimum: one rank per NUMA domain on thismachine, and L3's shape, so
L2b→L3differs only in the node count. The section says so ratherthan implying a sweep.
Two findings recorded next to the table:
0.2–0.6 GiB on the Schrödinger ones, on one shape, one node and one binary. The flag sets
differ in the observable — 14.75M terms against 200k. The raw observable is well under a GiB, so
something indexed by it scales with it; that is not attributed further here.
paid
Rtimes with no extra nodes to spread it over. An earlier draft of this change claimed arow fitting L2a fits L2b; the measurement refutes it and the claim is gone.
~GiB/nodeis a sum over ranks, as L3's column is, so reading it against L2a's single-process peakwould be a sum-against-max comparison — the error that produced a phantom 2.3× in this project
before. The table says so. Wall time is not compared across the two rungs: the same 1×128 work
measured 140 s in L2a's job and 176 s in another allocation, which is exactly why that comparison
needs both arms in one job.
Also in this push: a new trap —
Pabove the cores a rank can see, which is what carrying L2a'smonoprop_PARTITIONSinto an L2b launch does — and the multi-rank skeleton now names theinterpreter directly, because
uv runre-resolves the environment in every rank.Stacked on top: #323 rebuilds the continuous benchmarking around these rungs.
Verification
uv run pytest: 622 passed, 8 skipped — unchanged from chore(benches): four operations, three measures #316.prek run --all-filesover every changed file: clean.ERROR: monoprop_PARTITIONS is unset on a run of 2 ranks…from both ranks, exit 4; set to 8 → runs, and{"ranks": 2, "nodes": 1, "ranks_per_node": 2, "partitions_env": "8", "monoprop_threads": "8"}lands inresults/<label>.jsonand in the report's Configuration table. One rank with the knob unset still runs.--pauli-num-qubits=100and--hubbard-observable-site=70each fail with the message naming the constraint.LADDER.mdchecked againstpytest benches --help; all present. Every-krun through--collect-only; each selects exactly the intended node ids.🤖 Generated with Claude Code