fix: run scheme compute on a dedicated large-stack rayon pool - #326
Draft
markosg04 wants to merge 2 commits into
Draft
fix: run scheme compute on a dedicated large-stack rayon pool#326markosg04 wants to merge 2 commits into
markosg04 wants to merge 2 commits into
Conversation
Prover kernels recurse inside rayon parallel iterators (the bridge splitter re-splits whenever a job migrates to a stealing worker) with heavyweight frames; sampling a prove shows the deepest chains bottoming out in the setup XOF matrix expansion (derive_public_matrix_flat). On rayon's default 2 MiB worker stacks this overflows nondeterministically in downstream library use — observed in jolt CI and reproduced at ~40% of runs locally (a16z/jolt#1676) — while the in-tree e2e suite never sees it because it installs a 256 MiB global pool (init_rayon_pool) and 256 MiB test threads (run_on_large_stack). Every AkitaCommitmentScheme entry point now funnels through run_on_compute_pool: a lazily built pool with 64 MiB, named workers (virtual-memory cost only), with a serial fallback when the parallel feature is off. batched_prove gains + Sync on its LevelProveStacks parameter (the concrete stacks already cross worker threads inside the kernels). A new default_stack_repro test exercises a jolt-shaped one-hot prove on the default global pool — the boundary the rest of the suite deliberately avoids. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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
Prover/verifier compute can overflow rayon's default 2 MiB worker stacks, nondeterministically, when akita is used as a library. This PR makes the stack requirement library-owned: every
AkitaCommitmentSchemeentry point now runs on a dedicated, lazily-built rayon pool with 64 MiB worker stacks (run_on_compute_poolinakita_prover::compute), and adds a regression test that exercises the default-pool boundary the in-tree suite never touches.Draft on purpose — the diagnosis is solid but there is a real design question about where you want the bound to live (see Open questions).
The failure, as observed downstream
Jolt drives this code through
jolt-akita(fp128 D64, one-hot K=16/K=256 and dense flavors) and hit worker-thread stack overflows during proving:fatal runtime error: stack overflow, aborting/ SIGABRT on an unnamed thread, killing the process mid-prove — once in GitHub Actions CI (ubuntu, during proof-fixture generation) and reproducibly on an M-series Mac at small trace shapes, where it failed ~40% of runs of an identical test (feat: Akita lattice PCS integration — jolt-prover-legacy packed prover a16z/jolt#1676, commitsa2e2b21…f7172c1carry the details and the downstream mitigation).rayon::iter::plumbing::bridge_producer_consumer::helperon a rayon worker (guard-page hit; the frame chain doesn't unwind).RUST_MIN_STACK=134217728makes it vanish, which fattens std-spawned rayon workers — consistent with a pure stack-depth failure.Why the in-tree suite never sees it
The e2e suite compensates globally:
init_rayon_pool()installs a 256 MiB global rayon pool andrun_on_large_stack()runs each test body on a 256 MiB thread (crates/akita-pcs/tests/akita_e2e.rs,tests/common/mod.rs). So the library's true stack appetite is invisible to CI here, while library callers inherit rayon's 2 MiB defaults.Where the depth comes from
Sampling a live prove at a jolt-shaped input (one-hot K=16, 17 vars × 56 polys) shows the deepest chains are
bridge_producer_consumer::helper→join_contexttowers bottoming out in the setup XOF matrix expansion —akita_types::proof::setup::derive_public_matrix_flat's per-entry SHAKE256 sampling (LabeledMatrixXof::entry_rng), whose closure frames carry keccak/digest state through every split level. Other kernels likely contribute; this one is the deepest chain I could observe directly.The change
akita_prover::compute::run_on_compute_pool<R: Send>(f: impl FnOnce() -> R + Send) -> R: aOnceLockrayon pool withstack_size(64 MiB)(lazily committed — virtual address space only) and named workers (akita-compute-N, so any future overflow identifies itself). Serial no-op fallback undernot(feature = "parallel").AkitaCommitmentSchemepublic entries funnel through it:setup_prover,setup_verifier,commit,batched_commit,commit_group,commit_final_group,batched_prove,batched_verify.batched_provegains+ Syncon itsimpl LevelProveStacksparameter (the concrete stacks already cross worker threads inside the kernels).akita-pcs/tests/default_stack_repro.rs: a jolt-shaped one-hot prove loop on the default global pool — deliberately not usinginit_rayon_pool/run_on_large_stack.Honesty note on the test: on my machine it passes both before and after this change (7/7 pre-fix runs) — the overflow needs less favorable stealing dynamics than a standalone loop produces (jolt's mixed workload and CI's core counts hit it). It still guards the boundary: it is the only test in the suite that exercises library entry points on default stacks, and it turns any future regression here into a named-thread backtrace instead of a downstream mystery. A deterministic downstream repro exists: jolt @
f7172c1with its pool wrap and trace-padding floor reverted fails ~40% of runs.Open questions
with_min_lenon the XOF expansion loop and heap-allocating its digest scratch — which would make default stacks safe without owning a pool. Happy to rework in that direction if preferred.ComputeBackendSetup::prepare_setupis not wrapped (callers like jolt invokeCpuBackend.prepare_setupdirectly). Should it be?init_rayon_pool/run_on_large_stackworkarounds can probably be retired in a follow-up, which would make CI exercise the same stacks callers get.Related (not in this PR)
The folded-only cutover (#311/#317) also removed all proof paths for small openings — measured minimum plannable
num_varsatfc20716: dense ×1–2 → 13, dense ×56 → 19, one-hot ×56 → 16 ("no schedule with at least two folds"). That silently broke sub-4096-cycle traces downstream (jolt now pads toT ≥ 2^12). Happy to file it as a separate issue with the full probe data if useful.🤖 Generated with Claude Code