perf(transcript): sample FRI query indices via a masked bit reservoir#817
perf(transcript): sample FRI query indices via a masked bit reservoir#817diegokingston wants to merge 4 commits into
Conversation
FRI query indices live in the folded LDE domain of size `lde_length / 2`, which is always a power of two. Because the range is a power of two, taking the low `k = log2(domain/2)` bits of uniform hash output is exactly uniform — no rejection sampling is needed. The current `sample_u64(domain>>1)` path already produces the right (unbiased) values, but does so wastefully: one full Keccak squeeze per query, of which only ~k of 256 bits are used. Add `sample_bits(bits)` backed by a byte reservoir on `DefaultTranscript`: one 32-byte squeeze now serves ~256/k query indices instead of one Keccak permutation each — a ~10x reduction in query-phase permutations (8 vs 80 for 80 queries at k=24; see `sample_bits_amortizes_keccak_squeezes`). This is purely a performance change (same range, unbiased either way); it matters most for the recursion-guest verifier, where each permutation is a precompile ecall. This mirrors Plonky3, which samples FRI indices with `sample_bits` (mask, no rejection) over a hash-refilled reservoir, reserving rejection for prime-field samples. - `IsTranscript::sample_bits(bits)` with a safe default (`sample_u64(1<<bits)`) so other transcript impls keep compiling; `DefaultTranscript` overrides it with the reservoir version. - The reservoir is cleared on every absorb (`append_bytes`), so sampled bits stay bound to the full prior transcript (matches Plonky3's HashChallenger, which clears its output buffer on `observe`). `state()` is unchanged (hasher-only); the reservoir is always empty at absorb/serialize boundaries. - Both `sample_query_indexes` sites (prover + verifier) switch to `sample_bits`; they must — and do — stay bitwise identical. - Adds a `keccak_squeezes()` profiling counter used by the amortization test. Note: this changes the Fiat-Shamir sampling schedule, so existing serialized proofs won't verify; prove+verify are updated together (roundtrip tests pass).
|
/bench-verify |
|
⏳ Benchmark started on the bench server. The verifier bench takes ~5 min; the recursion-guest cycle comparison then adds guest builds — a few minutes when cached, up to ~1h on a cold run. The bench server is occupied until it finishes. |
Verifier benchmark —
|
| Metric | main | PR | Δ |
|---|---|---|---|
| Verify time (per-side) | 3.891s | 3.888s | -0.08% ⚪ |
| Proof size | 230.99 MiB | 230.99 MiB | +0.00% ⚪ |
Per-side (
⚠️ PR REJECTS the baseline's valid proof — likely a VERIFY REGRESSION, not a format change): A/B/B/A cancels machine drift but not proof-specific variance — read the Verify-time Δ as approximate.
pairs: 20 mean A (PR): 3.888s mean B (main): 3.891s
[parametric] paired-t mean -0.08% sd 0.66% se 0.15%
95% CI: [-0.39%, +0.23%] (t df=19 = 2.093)
[robust] median +0.09% Wilcoxon W+=110 W-=100 p(exact)=0.8695 (z=+0.17)
run-to-run jitter: A CV 0.43% B CV 0.51% (lower = steadier)
within-session drift: -0.17% over the run, 1st->2nd half +0.05%
⚪ INCONCLUSIVE — effect not separable from 0 at n=20 (point estimate ~+0.09%). Add pairs to resolve.
Drift-free interleaved A/B/B/A measurement. - = PR faster. Trust the verdict when paired-t and Wilcoxon agree.
Recursion guest cycles (main vs PR)
=== Recursion-guest cycle comparison — single query (blowup=2, 1 query) — deterministic to ~±100k cycles ===
REF_B (baseline) origin/main c511b31 guest=recursion-min.elf
REF_A (PR) d5c4ebf d5c4ebf guest=recursion-min.elf
| Metric | REF_B (baseline) | REF_A (PR) | Δ (A-B) |
|---|---|---|---|
| Guest cycles | 99.5M | 99.1M | -0.4M (-0.39%) |
| Keccak calls | 3885 | 3884 | -1 |
note: cycles reproduce to ~±100k (build codegen + proof nondeterminism); treat sub-100k deltas as noise, not signal.
raw (exact integer counts)
ref_b_sha=c511b31b7ff264ee69f536e3c6b5f40602953837 ref_b_elf=recursion-min.elf ref_b_cycles=99491236 ref_b_keccak=3885 ref_b_execute_wall_s=2
ref_a_sha=d5c4ebf00024887bef7d45db5dc371171ac40ad4 ref_a_elf=recursion-min.elf ref_a_cycles=99099958 ref_a_keccak=3884 ref_a_execute_wall_s=2
delta_cycles=-391278 delta_keccak=-1
MauroToscano
left a comment
There was a problem hiding this comment.
Putting this on pause for now: measured at the production regime (see the "Measured" section in the description), the improvement is too small right now — −0.32% guest cycles / −0.47% keccak — to prioritize a Fiat–Shamir sampling-schedule change. The approach and numbers check out; we'll revisit when the bigger levers have landed.
No description provided.