Skip to content

Feat/isolated pool parallelism#480

Closed
diegokingston wants to merge 9 commits into
mainfrom
feat/isolated-pool-parallelism
Closed

Feat/isolated pool parallelism#480
diegokingston wants to merge 9 commits into
mainfrom
feat/isolated-pool-parallelism

Conversation

@diegokingston

Copy link
Copy Markdown
Collaborator

No description provided.

Proposes replacing the global Rayon pool chunks-of-K approach in
multi_prove Rounds 2-4 with S isolated thread pools, each processing
a load-balanced partition of tables on dedicated cores. Targets 3-4x
improvement for Rounds 2-4 on 32+ core machines without changing the
proof format or verifier.
7-task plan for replacing the global Rayon pool in multi_prove Rounds 2-4
with S isolated thread pools. Reviewed and fixed for borrow checker safety,
instruments TLS correctness, and unsafe soundness.
Replace the chunk-based parallel proving (K workers on global pool) with
isolated Rayon ThreadPool partitions. Tables are partitioned into S groups
via LPT scheduling (partition_tables_by_cost), each group runs on a
dedicated ThreadPool with cores/S threads, eliminating cross-table
contention on the global Rayon pool.

Key changes:
- Drop Phase A/C pool_sets before Rounds 2-4, allocate S fresh r24_pool_sets
- std::thread::scope spawns S OS threads, each calling pool.install(...)
- SendPtr wrapper enables safe disjoint mutable access to per-table transcripts
- Proofs collected with original indices, sorted for deterministic output order
- instruments feature: indexed timings sorted to match proof order
- Non-parallel fallback: sequential loop using r24_pool_sets[0]
Ensures each partition has at least 2 tables, avoiding wasteful
pool creation for single-table partitions.
Move pool-return (into_columns + move-back) before the error match,
so subsequent tables in the same partition get valid buffers even if
a prior table's proving fails.
@diegokingston

Copy link
Copy Markdown
Collaborator Author

/bench 3

@github-actions

github-actions Bot commented Apr 8, 2026

Copy link
Copy Markdown

Codex Code Review

  1. [Medium][Security] Unsound cross-thread marker on raw pointer wrapper
  • File: prover.rs, prover.rs
  • SendPtr<T> is marked Send/Sync for all T:
    unsafe impl<T> Send for SendPtr<T> {}
    unsafe impl<T> Sync for SendPtr<T> {}
    This is too broad and can become UB if reused with non-Send types.
  • Fix: constrain impls (unsafe impl<T: Send> Send ..., unsafe impl<T: Send> Sync ...) or avoid a reusable generic and keep the raw-pointer hack local to the exact transcript type.
  1. [Medium][Bug/Perf] Unbounded shard count can create excessive thread pools and panic
  • File: prover.rs, prover.rs, prover.rs
  • s is derived from env var and capped only by num_airs / 2, not by CPU count. With large SHARD_PARALLELISM and many AIRs, this can create many pools/threads (num_threads=1 each), causing heavy oversubscription or pool build failure, then panic via expect(...).
  • Fix: clamp s to cores (or a stricter practical max), and return a ProvingError instead of panicking on pool creation failure.

No other concrete security/logic regressions stood out in the PR diff.

// SAFETY: The caller guarantees disjoint access — no two threads dereference the
// same index. The raw pointer itself is just an address; sending it is safe.
#[cfg(feature = "parallel")]
unsafe impl<T> Send for SendPtr<T> {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — Unsound Send/Sync impl: missing T: Send bound

unsafe impl<T> Send for SendPtr<T> is unsound because it allows wrapping non-Send types (e.g. *mut Rc<Transcript>) and sending them across threads. The bound should require T: Send.

Ironically, the plan doc (2026-04-08-isolated-pool-parallelism.md) gets this right:

unsafe impl<T: Send> Send for SendPtr<T> {}
Suggested change
unsafe impl<T> Send for SendPtr<T> {}
unsafe impl<T: Send> Send for SendPtr<T> {}

#[cfg(feature = "parallel")]
unsafe impl<T> Send for SendPtr<T> {}
#[cfg(feature = "parallel")]
unsafe impl<T> Sync for SendPtr<T> {}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same issue — Sync impl also needs the T: Send bound (since we're sharing the ability to produce &mut T across threads, the pointed-to type must be Send).

Suggested change
unsafe impl<T> Sync for SendPtr<T> {}
unsafe impl<T: Send> Sync for SendPtr<T> {}

for chunk_start in (0..num_airs).step_by(k) {
let chunk_end = (chunk_start + k).min(num_airs);
let chunk_size = chunk_end - chunk_start;
let s = shard_parallelism().min(num_airs / 2).max(1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Medium — Bug: num_airs / 2 cap incorrectly limits parallelism

Integer division means num_airs / 2 floors to 0 when num_airs = 1 (saved by .max(1)), but produces 1 for num_airs = 2 or 3, so s = 1 always in those cases — no parallelism at all. For num_airs = 4, s is capped at 2 instead of 4.

The plan spec says .min(num_airs), which is the correct cap (at most one partition per table). Even if the goal is "at least 2 tables per partition", the right cap is still .min(num_airs)partition_tables_by_cost already handles empty partitions gracefully.

Suggested change
let s = shard_parallelism().min(num_airs / 2).max(1);
let s = shard_parallelism().min(num_airs).max(1);

})
.collect();

handles.into_iter().map(|h| h.join().unwrap()).collect()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Low — Panics instead of propagating ProvingError on thread panic

If any worker thread panics (e.g. due to a Rayon internal failure or an unexpected unwrap), .unwrap() here re-panics in the calling thread, bypassing the Result<_, ProvingError> error channel that the rest of multi_prove uses. The old chunked code collected errors as Results and surfaced them to the caller.

Not critical since ProvingError cases inside the partition are handled via results.push(Err(e)) and only genuine panics would hit this path — but worth noting the behavior change.

@github-actions

github-actions Bot commented Apr 8, 2026

Copy link
Copy Markdown

Benchmark — fib_iterative_8M (median of 3)

Table parallelism: 32 (auto = cores / 3)

Metric main PR Δ
Peak heap 250294 MB 250002 MB -292 MB (-0.1%) ⚪
Prove time 79.886s 76.171s -3.715s (-4.7%) ⚪

✅ No significant change.

✅ Low variance (time: 0.5%, heap: 0.3%)

Commit: 777c2c4 · Baseline: cached · Runner: self-hosted bench

@claude

claude Bot commented Apr 8, 2026

Copy link
Copy Markdown
Contributor

Review: Isolated Pool Parallelism (Rounds 2-4)

Good architectural direction — replacing the global-pool chunking with isolated ThreadPools eliminates cross-table thread contention and should give meaningful wall-time improvements on many-core machines. The LPT scheduler and std::thread::scope framing are the right tools for this.

Issues found

Medium — Two inline comments on SendPtr:

  • unsafe impl<T> Send and unsafe impl<T> Sync both lack a T: Send bound, making the impls unsound in general. The plan doc already shows the correct form (T: Send); the implementation just diverged from it.

Mediumshard_parallelism().min(num_airs / 2) (line 1878):

  • Integer division floors num_airs / 2 to 1 for num_airs ∈ {2, 3}, so s = 1 and no parallelism is ever used for small proving jobs. The plan spec says .min(num_airs). See inline comment.

Lowh.join().unwrap() (line 2029):

  • Thread panics bypass ProvingError and propagate as panics. The old code funnelled all failures through Result. See inline comment.

No issues with

  • The partition_tables_by_cost LPT scheduler — indices are provably disjoint, making the SendPtr safety argument valid (once the T: Send bound is added).
  • Pool-buffer reuse within partitions — columns are returned to the pool correctly even when prove_rounds_2_to_4 errors.
  • Deterministic output ordering via sort-by-index — correct.
  • Non-parallel fallback path — clean and consistent with the parallel path.

@gabrielbosio

Copy link
Copy Markdown
Collaborator

It seems this is affecting Rounds 2-4. We could also try applying this same technique for Round 1.

@gabrielbosio

Copy link
Copy Markdown
Collaborator

It seems this is affecting Rounds 2-4. We could also try applying this same technique for Round 1.

Just tested this, no changes.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants