Repository navigation
ppvm-lindblad: deterministic row-parallel matvec for the cached expm generator - #252
Open
AlexSchuckert wants to merge 2 commits into
Open
AlexSchuckert wants to merge 2 commits into
AlexSchuckert wants to merge 2 commits into
Conversation
…generator CscOp::dot split the columns into rayon::current_num_threads() chunks, accumulated each into a dense length-n partial vector and summed the partials on one core. The summation order, and hence the round-off, depended on the thread count; a rank-capped evolution amplifies that round-off to the size of the truncation error. It also allocated threads x n scalars per matvec and did an O(threads x n) serial reduction. The action pass now stages each entry in an exactly-sized bucket of its row range (a range size that depends on n only), and a second parallel pass counting-sorts every range into CSR, dropping the buckets as it goes. The matvec computes each output entry on one thread over its row in ascending column order, so exp(dt M) b is bit-identical for every thread count and equal to the previous single-thread result. No partial vectors, no serial reduction. Adds pc_step_is_bit_identical_across_thread_counts (fails on the old matvec at 2 threads). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The cache conversion preserves column accumulation order, and the review identified no blocking defects.
0 open findings
What changed in this PR
Replaces the cached Lindblad generator’s column-based matvec with row-parallel CSR accumulation to eliminate thread-dependent summation order.
Changes:
- Builds row-major caches and removes per-thread partial vectors and serial reduction.
- Adds a rank-capped evolution regression test comparing bitwise results across 1/2/3/8 threads.
| File | Description |
|---|---|
| crates/ppvm-lindblad/src/tests.rs | Adds cross-thread determinism coverage. |
| crates/ppvm-lindblad/src/mf_expm.rs | Implements CSR caching and row-parallel matvec. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…cache - compute_action_terms returns its terms sorted by (word_hash, Word). The drain order of the reused scratch FxHashMap follows its capacity, which carries over between calls on a rayon job and so depends on the thread count. Every caller sums or inserts in that order: the orbit-rep duplicate columns, raw/diag (hence the (m*, s) choice), and the leakage merge. - Checked u32 range on the per-range CSR nnz, replacing the dropped "CSC block exceeds u32 entries" guard. - Rows per range: a power of two with a 64-row floor (was max(1024, n/256)), so small and medium dims split into up to 256 tasks, and the range lookup in the build loop is a shift; the per-block counts buffer is reused via map_init. - The matvec skips zero inputs again, as the column-wise product did. - Docs: staging cost for complex T, row-major cache wording, `mat` naming. - Tests: action_term_order_is_independent_of_scratch_capacity (fails without the sort) and pc_step_orbit_rep_is_bit_identical_across_thread_counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
This makes
exp(dt·M)·binppvm-lindbladbit-identical for every rayon thread count. It does so by replacing the column-chunkedCscOp::dotwith a row-major (CSR) cache and a row-parallel matvec. I'm framing it as a determinism and scaling fix, not a speed-up.Problem.
CscOp::dotsplit the columns intorayon::current_num_threads()chunks, accumulated each chunk into a dense length-n partial vector, and summed the partials on one core. The summation order, and hence the round-off, depended on the thread count. A rank-capped evolution amplifies that round-off to the size of the truncation error. On the 82-qubit XX ladder (open two-leg ladder, centre-rung spin density, A = 3B,drop_tol = 0), changingRAYON_NUM_THREADSmoved the MSD by up to 1.6e-2 by Jt = 2. A stored run could therefore only be reproduced at the same core count. The design also allocated threads × n scalars per matvec and did an O(threads × n) serial reduction.Change.
dotcomputes each output entry on one thread, over its row in ascending column order.compute_action_terms(sorted by word hash), every sum in the cache build, the matvec and the leakage merge runs in an order fixed by the basis alone. Results differ frommainonly at round-off level (≤ 3.6e-15 in the ladder MSD by Jt = 2).Nothing else in the step depends on the thread count, for these reasons:
applyruns sequentially (parallel_hint = false).Tests.
pc_step_is_bit_identical_across_thread_counts: a rank-capped XXZ chain with Z dephasing (n = 14, cap 1200, admit 3600, 12 steps), compared at 1/2/3/8 threads. It fails onmainat 2 threads.pc_step_orbit_rep_is_bit_identical_across_thread_counts: the same check on the orbit-rep path (ring with collective decay, cap binding).action_term_order_is_independent_of_scratch_capacity:compute_action_termsmust return the same order from a fresh and from a pre-grown scratch map. It fails without the sort. Allppvm-lindbladtests and the Python lindblad tests pass, andcargo fmtandclippy --all-targetsare clean.Measurements
Apple M4 (4P + 6E cores), 82-qubit XX ladder, T = 2, δ = 0.025.
Revisions covered. The baseline is
mainat1d78a44b. The determinism, cost and breakdown numbers below were measured on the first revision,a73875f6. For the current revision,a5271fcf(canonical term order and review fixes), I re-verified bit-identity across 1/2/4/8/10 threads at B = 2^12–2^14 and peak RSS at 2^16/2^18. Its wall times are still to follow: the machine was shared with other jobs while I measured.Determinism, B = 2^12–2^16, 1/2/4/8(6)/10 threads:
a73875f6was also identical tomainat 1 thread;a5271fcfdiffers from it by ≤ 3.6e-15 in MSD.main: max|ΔMSD| vs its own 1-thread run is up to 1.6e-2 (B = 2^13), 5.0e-3 (2^14) and 1.9e-3 (2^16).Cost, 6 threads, two interleaved repetitions:
Expm breakdown at 2^18 / 6 threads (n = 786k, nnz = 4.4M):
At 2^16 the time per matvec (ms) scales with threads as follows:
The old matvec degrades above 4 threads; this one keeps scaling.
Costs, stated honestly:
Deliberately not included
Canonical (|c|, word) tie-breaking in
cap_basis/cap_map_to_room.Leakage merge. In local profiles, the leakage merge and room cap are the next serial bottleneck, about 60% of the wall at 10 threads. A separate branch addresses that.
🤖 Generated with Claude Code