build, cli, rust: ThinLTO multisig-release + --rayon-threads + leanMultisig 5eba3b1 bump - #903
Conversation
…flag Two leverage points the #899 investigation has not yet exercised on the production aggregator image: 1. **Dedicated [profile.multisig-release] Cargo profile**. The shared [profile.release] forces `lto = false, codegen-units = 16` to avoid `sys_alloc_aligned`-style symbol collisions between risc0 and openvm. That constraint does not apply to the `--prover=dummy` build — it links neither risc0 nor openvm (features: `libp2p,hashsig,multisig`), so cross-crate LTO is safe. The new `multisig-release` profile inherits `release` and sets `lto = "thin", codegen-units = 1` so the leanMultisig prover hot path gets the same level of inlining that the single-prover risc0/openvm builds already get. Opt-level stays at 3 — switching to `s`/`z` with CGU=1 miscompiles the prover on x86_64 Linux (zeam#734, leanEthereum/leanVM#198). build.zig is updated so `-Dprover=dummy` cargo-builds with `--profile multisig-release` and the static lib is read from `rust/target/multisig-release/libzeam_glue.a`. Local rebuild from scratch shrinks `libzeam_glue.a` from 91M → 27M (≈3.4x), which is consistent with cross-crate dead-code elimination actually running. 2. **`--rayon-threads` flag**. `pkgs/cli/src/node.zig` currently splits the post-system-thread budget roughly half-and-half between the Zig thread pool and the rayon pool used by the multisig prover. That split is appropriate for non-aggregator nodes (where rayon is also entered from verification on Zig workers), but on a CPU-rich aggregator the produce-path FFI is the bottleneck and a larger rayon pool measurably shortens per-pass build time. Today the only way to bump rayon is a rebuild. This adds `--rayon-threads N` (default unset = current behaviour) so operators can re-tune the split per-deployment. We also log `cpu_count`, the zig worker count and the chosen rayon thread count at startup so the same number is visible from logs without `cat`-ing `/proc/$PID/status`. Behaviour is unchanged unless the flag is passed. Related: #899
|
Adversarial review started from Telegram request. I’ll inspect the diff and post findings here. |
|
Adversarial review — no blocking defects, but several things worth naming explicitly. ✅ What is correct and well-reasoned
|
…ubscription Addresses two review comments on #903: 1. Cache pollution: include `rust/Cargo.toml` in every Swatinem rust-cache key. PR #903 added a new `multisig-release` cargo profile and pointed the default (dummy) prover at `rust/target/multisig-release/`. The old keys hashed only `Cargo.lock`, so caches saved before this PR would restore without that profile dir on the first post-merge run. Hashing `rust/Cargo.toml` invalidates the cache automatically on any future profile add/remove/rename. 2. --rayon-threads typo guard: log a `warn` when the operator-supplied thread count exceeds the detected `cpu_count`. Rayon tolerates over-subscription, but values like `--rayon-threads 160` on a 4-vCPU box silently degrade throughput. We warn rather than reject so cgroup quota edge cases (where `getCpuCount` over-reports the container's effective CPUs) still work.
|
Thanks @zclawz — both items addressed in 4d5ae4d. 1. Cache pollution. Bumped every key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock', 'rust/Cargo.toml') }}Six cache scopes touched ( 2. if (options.rayon_threads) |override| {
if (@as(usize, override) > cpu_count) {
self.logger.warn(
"--rayon-threads {d} exceeds detected cpu_count={d}; rayon over-subscription typically reduces throughput. Verify this is intentional.",
.{ override, cpu_count },
);
}
}Warn rather than hard-reject because cgroup-limited containers commonly see Pre-commit checks all green: |
|
Re-review after 4d5ae4d: LGTM from code review. The two concerns from my adversarial review are addressed:
I re-checked the full diff and don’t see new correctness issues:
CI is still in progress on the latest commit at the time of this comment, but from code review I’m happy with the PR. Per repo convention, this is a regular LGTM comment rather than a formal GitHub Approve review. |
|
Thanks — this looks good to me. Hashing |
Devnet operators reported ~16 sig/s aggregation throughput on the
previous pin (2eb4b9d, Apr 17) versus ~37 sig/s on the leanMultisig
benchmarks against 5eba3b1. The dominant contributor is leanMultisig
commit 939a767, which removed superfluous `#[inline]` annotations in
`eq_mle.rs` for a measured +10% on AVX-512 hardware (Hetzner AX42-U,
same class as the devnet aggregator hosts) with no NEON regression.
Other commits in the bump range:
- 7a71c0f: eq_mle base-case correctness fix (was using packing_width
instead of log_packing_width)
- 5fbd5bf: Plonky3 PR #1600 NEON dot-product regression coverage
- e5c2183: leanSig dep flipped to `main` (devnet4 merged into main)
- 5eba3b1: rec_aggregation BenchmarkReport (API-additive, source of the
per-node breakdown that lean-bench reads)
leanSig:main renamed `SchemeAbortingTargetSumLifetime32Dim46Base8` →
`SIGAbortingTargetSumLifetime32Dim46Base8` in `lifetime_2_to_the_32`
only. The test-only `lifetime_2_to_the_8` instantiation kept its
`Scheme...` name unchanged, so only the production config import in
`hashsig-glue` needs adjustment.
anshalshukla
left a comment
There was a problem hiding this comment.
Approving it although I don't like how we are using rayon threads mingled with zig thread pool but that has already moved into the codebase so maybe a proper cleanup can be done later with proper profiling of threads
Summary
Three build/runtime tuning levers the #899 investigation has not yet exercised on the production aggregator image. All are conservative defaults — behaviour is unchanged unless an operator opts in (items 1–2) or unchanged on the spec level (item 3).
1.
[profile.multisig-release]Cargo profileThe shared
[profile.release]is forced tolto = false, codegen-units = 16to avoidsys_alloc_aligned-style symbol collisions between risc0 and openvm. That constraint does not apply to the--prover=dummybuild — it links neither risc0 nor openvm (--features libp2p,hashsig,multisig), so cross-crate LTO is safe.The new
multisig-releaseprofile inheritsreleaseand setslto = "thin", codegen-units = 1so the leanMultisig prover hot path gets the same level of inlining that single-prover risc0/openvm builds already get.opt-levelstays at the inherited3— switching tos/zwithCGU=1miscompiles the prover on x86_64 Linux (see[profile.openvm-release]comment inrust/Cargo.toml, zeam#734, leanEthereum/leanVM#198).build.zigis updated so-Dprover=dummycargo-builds with--profile multisig-releaseand the static lib is read fromrust/target/multisig-release/libzeam_glue.a.Observed locally (fresh build, x86-64-v3 default target-cpu):
release)multisig-release)libzeam_glue.a3.4x size reduction is consistent with cross-crate dead-code elimination actually running; we expect a similar shape on prover throughput.
2.
--rayon-threads NCLI flagpkgs/cli/src/node.zigsplits the post-system-thread budget roughly half-and-half between the Zig thread pool and the rayon pool used by the multisig prover. That split is appropriate for non-aggregator nodes (where rayon is also entered from verification on Zig workers), but on a CPU-rich aggregator the produce-path FFI is the bottleneck and a larger rayon pool measurably shortens per-pass build time. Today the only way to bump rayon is a rebuild.This adds
--rayon-threads N(default unset = current behaviour) so operators can re-tune the split per-deployment without rebuilding. We also logcpu_count, the zig worker count, and the chosen rayon thread count at startup so the same number is visible from logs withoutcat-ing/proc/$PID/status:Operator-typo guard (added after review feedback): if
--rayon-threadsexceedscpu_count, we log awarnline. We don't reject because cgroup-limited containers commonly seegetCpuCountover-report relative to the effective quota — see the comment inpkgs/cli/src/node.zigfor the cases that motivated warn-not-reject.3. leanMultisig pin bump
2eb4b9d→5eba3b1(devnet4 head)Folded in from #905 (closed). Devnet operators reported ~16 sig/s aggregation throughput on the previous pin (Apr 17) versus ~37 sig/s on the leanMultisig benchmarks against
5eba3b1(May 12) on the same hardware. The dominant contributor is leanMultisig commit939a767, which removed superfluous#[inline]annotations ineq_mle.rsfor a measured +10% on AVX-512 (Hetzner AX42-U, same hardware class as the devnet aggregator hosts) with no NEON regression. Splitting this out would have forced two image cuts and two redeploys to capture the full aggregator-perf win, so it belongs with items 1–2.Commits picked up (2eb4b9d → 5eba3b1):
0fbf27c89e0320→6a0d8fad853f1c939a767#[inline]ineq_mle.rs(+10% AVX-512, neutral NEON)7a71c0fpacking_widthinstead oflog_packing_width)5fbd5bfe5c2183main(devnet4 merged into main)5eba3b1rec_aggregation: expose structuredBenchmarkReport(port from devnet5)API adjustment in
hashsig-glue.e5c2183pulls inleanSig:main, which renamedSchemeAbortingTargetSumLifetime32Dim46Base8→SIGAbortingTargetSumLifetime32Dim46Base8in the productionlifetime_2_to_the_32instantiation. The test-onlylifetime_2_to_the_8kept the originalScheme...name, so only the production config import needs to flip; thetest-configandtest_schemepaths are untouched. No other zeam code references the old type name.Out of scope: recursive-aggregation review. The same review observation that flagged this bump also noted that, per spec, aggregators run recursive aggregation whenever helper payloads are available — 1.5–6 s wall — versus <600 ms for the non-recursive (gossip-only) fast-path that zeam already takes when
selected_children.items.len <= 1(pkgs/types/src/block.ziglines 651–665). Whether to widen that fast-path to always skip recursion on the produce path is a spec/operational design choice and is intentionally not part of this PR. Tracked separately.Test plan
zig fmt --check .cargo fmt --manifest-path rust/Cargo.toml --all -- --checkcargo clippy --manifest-path rust/Cargo.toml --workspace --no-default-features --features=libp2p,hashsig,multisig -- -D warningszig build -Dprover=dummy— clean build withmultisig-releaseprofile; producesrust/target/multisig-release/libzeam_glue.aat 27Mzig build test --summary all— all targets pass (xmss FFI test at 53 s, the one that exercises any ABI break from the dep bump)zig build simtest --summary all— passed (combined branch)-Dprover=risc0/-Dprover=openvm/-Dprover=allbuilds — those arms are unchanged but worth confirming the dispatch inaddRustGlueLibstill picks the right per-prover artifactlean_committee_signatures_aggregation_time_secondsp50zeam_aggregate_skip_total{reason="in_flight"}— should fall toward 0 since fewer passes will spill across the next slotRelated
e99573b8)