Skip to content

Commit 0b6de00

Browse files
committed
refactor: address review on the base-field first sumcheck round
- prover: precompute 1/2 once and pass it into combined_round_message instead of recomputing a field inverse every round (transcript-identical) - deps: move inferno/tracing-flame to root [workspace.dependencies] - fixtures: relocate the single-commit timed prove into harness.rs and point the flamegraph probe at target/profile/
1 parent 3097cab commit 0b6de00

5 files changed

Lines changed: 88 additions & 93 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ chrono = "0.4.41"
123123
# This is a workaround because different package selection based on target does not mix well with workspace dependencies.
124124
divan = "0.1.21"
125125
hex = "0.4.3"
126+
inferno = { version = "0.12", default-features = false }
126127
itertools = "0.14.0"
127128
num-bigint = "0.4"
128129
paste = "1.0.15"
@@ -153,6 +154,7 @@ tokio-util = "0.7.13"
153154
tower = "0.5.2"
154155
tower-http = { version = "0.6.6", features = ["cors", "timeout", "trace"] }
155156
tracing = "0.1.41"
157+
tracing-flame = "0.2"
156158
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "ansi"] }
157159
tracing-tracy = "=0.11.4"
158160
tracy-client = "=0.18.0"

provekit/prover/src/whir_r1cs.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use {
22
::tracing::instrument,
33
anyhow::{ensure, Result},
4-
ark_ff::Field,
4+
ark_ff::{Field, One},
55
ark_std::{
66
rand::distributions::{Distribution, Standard},
77
Zero,
@@ -525,6 +525,10 @@ where
525525
// Prove that sum of F + ρ·G over the boolean hypercube equals ρ·Σ(G).
526526
let mut saved_val_for_sumcheck_equality_assertion = rho * sum_g_reduce;
527527

528+
// 2 is invertible in any field of odd characteristic; precompute 1/2 once
529+
// and reuse it for each round's cubic-coefficient solve.
530+
let half = M::Target::one() / (M::Target::one() + M::Target::one());
531+
528532
// First round: a, b, c are base-field, so the constraint products stay in
529533
// the base field with one mixed mul per point against the ext eq.
530534
let hhat = mixed_sumcheck_map_reduce([&a[..], &b[..], &c[..]], &eq, |[a, b, c], eq| {
@@ -543,6 +547,7 @@ where
543547
hhat,
544548
g_poly,
545549
rho,
550+
half,
546551
saved_val_for_sumcheck_equality_assertion,
547552
);
548553
saved_val_for_sumcheck_equality_assertion = saved_val;
@@ -582,6 +587,7 @@ where
582587
hhat,
583588
g_poly,
584589
rho,
590+
half,
585591
saved_val_for_sumcheck_equality_assertion,
586592
);
587593
saved_val_for_sumcheck_equality_assertion = saved_val;
@@ -606,6 +612,7 @@ fn combined_round_message<F: Field + Codec, S: DuplexSpongeInterface<U = u8>>(
606612
hhat: [F; 3],
607613
g_poly: [F; 4],
608614
rho: F,
615+
half: F,
609616
saved_val_for_sumcheck_equality_assertion: F,
610617
) -> (F, F) {
611618
let [hhat_i_at_0, hhat_i_at_em1, hhat_i_at_inf_over_x_cube] = hhat;
@@ -617,12 +624,11 @@ fn combined_round_message<F: Field + Codec, S: DuplexSpongeInterface<U = u8>>(
617624
let g_at_minus_one = g_poly[0] - g_poly[1] + g_poly[2] - g_poly[3];
618625
let combined_at_em1 = hhat_i_at_em1 + rho * g_at_minus_one;
619626

620-
let two = F::one() + F::one();
621627
combined_hhat_i_coeffs[2] = (saved_val_for_sumcheck_equality_assertion + combined_at_em1
622628
- combined_hhat_i_coeffs[0]
623629
- combined_hhat_i_coeffs[0]
624630
- combined_hhat_i_coeffs[0])
625-
/ two;
631+
* half;
626632

627633
combined_hhat_i_coeffs[3] = hhat_i_at_inf_over_x_cube + rho * g_poly[3];
628634

tooling/provekit-fixtures/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ whir.workspace = true
2424
# Concrete proof fields to instantiate the generic harness.
2525
provekit-backend-bn254.workspace = true
2626
provekit-backend-goldilocks.workspace = true
27-
inferno = { version = "0.12", default-features = false }
27+
inferno.workspace = true
2828
tracing.workspace = true
29-
tracing-flame = "0.2"
29+
tracing-flame.workspace = true
3030
tracing-subscriber.workspace = true
3131

3232
[lints]

tooling/provekit-fixtures/src/harness.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,67 @@ where
287287
scheme.prove_noir(merlin, r1cs_owned, vec![c1, c2], full_witness, &public)?;
288288
Ok(start.elapsed())
289289
}
290+
291+
/// Pre-built inputs for a timed single-commit prove, constructed outside the
292+
/// timer/flamegraph so scheme construction and the witness clones are not
293+
/// measured. Opaque: build with [`prove_setup`], consume with
294+
/// [`time_prove_core`].
295+
pub struct ProveInputs<P: FieldHash> {
296+
scheme: WhirR1CSScheme<P>,
297+
r1cs: R1CS<Base<P>>,
298+
witness_commit: Vec<Base<P>>,
299+
witness_prove: Vec<Base<P>>,
300+
}
301+
302+
/// Build the scheme and owned witness copies for a single-commit prove. Kept
303+
/// separate from [`time_prove_core`] so a profiler can install its subscriber
304+
/// between setup and the timed region (only the latter is captured).
305+
pub fn prove_setup<P>(r1cs: &R1CS<Base<P>>, witness: &[Base<P>]) -> ProveInputs<P>
306+
where
307+
P: FieldHash,
308+
Standard: Distribution<Ext<P>> + Distribution<Base<P>>,
309+
{
310+
let scheme = WhirR1CSScheme::<P>::new_for_r1cs(r1cs, witness.len(), 0, Vec::new(), true, HASH);
311+
ProveInputs {
312+
scheme,
313+
r1cs: r1cs.clone(),
314+
witness_commit: witness.to_vec(),
315+
witness_prove: witness.to_vec(),
316+
}
317+
}
318+
319+
/// Time the single-commit proving core — `commit` + `prove_noir` — from
320+
/// pre-built inputs, returning the elapsed time alongside the proof and scheme.
321+
pub fn time_prove_core<P>(
322+
inp: ProveInputs<P>,
323+
public_inputs: &PublicInputs<Base<P>>,
324+
) -> Result<(Duration, WhirR1CSProof, WhirR1CSScheme<P>)>
325+
where
326+
P: FieldHash,
327+
Standard: Distribution<Ext<P>> + Distribution<Base<P>>,
328+
{
329+
let ProveInputs {
330+
scheme,
331+
r1cs,
332+
witness_commit,
333+
witness_prove,
334+
} = inp;
335+
336+
// Transcript binding derives from the scheme; built before the timer starts.
337+
let instance = public_inputs.hash_bytes::<P>(HASH);
338+
let ds = scheme.create_domain_separator().instance(&instance);
339+
let num_witnesses = r1cs.num_witnesses();
340+
let num_constraints = r1cs.num_constraints();
341+
342+
let start = Instant::now();
343+
let mut merlin = ProverState::new(&ds, P::transcript_sponge(HASH));
344+
let commitment = scheme.commit(
345+
&mut merlin,
346+
num_witnesses,
347+
num_constraints,
348+
witness_commit,
349+
true,
350+
)?;
351+
let proof = scheme.prove_noir(merlin, r1cs, vec![commitment], witness_prove, public_inputs)?;
352+
Ok((start.elapsed(), proof, scheme))
353+
}

tooling/provekit-fixtures/tests/profile.rs

Lines changed: 11 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -12,101 +12,22 @@
1212
1313
use {
1414
ark_std::rand::distributions::{Distribution, Standard},
15-
provekit_common::{
16-
Base, Ext, FieldHash, HashConfig, PublicInputs, PublicInputsHash, WhirR1CSProof,
17-
WhirR1CSScheme, R1CS,
15+
provekit_common::{Base, Ext, FieldHash, PublicInputs},
16+
provekit_fixtures::{
17+
builders::squaring_chain,
18+
harness::{prove_setup, time_prove_core},
1819
},
19-
provekit_fixtures::builders::squaring_chain,
20-
provekit_prover::WhirR1CSProver,
2120
provekit_verifier::WhirR1CSVerifier,
2221
std::{
2322
collections::HashMap,
2423
path::PathBuf,
2524
time::{Duration, Instant},
2625
},
27-
whir::transcript::ProverState,
2826
};
2927

3028
/// Log2 witness sizes to sweep.
3129
const SIZES: [u32; 4] = [14, 16, 18, 20];
3230

33-
/// Instance-binding hash for the probes (matches the harness default).
34-
const HASH: HashConfig = HashConfig::Sha256;
35-
36-
/// Inputs for a single-commit prove, built outside the timer so scheme
37-
/// construction and the `commit`/`prove_noir` ownership copies aren't measured.
38-
struct ProveInputs<P: FieldHash> {
39-
scheme: WhirR1CSScheme<P>,
40-
r1cs_owned: R1CS<Base<P>>,
41-
witness_commit: Vec<Base<P>>,
42-
witness_prove: Vec<Base<P>>,
43-
num_witnesses: usize,
44-
num_constraints: usize,
45-
}
46-
47-
/// Build the scheme + ownership copies (excluded from the timer and
48-
/// flamegraph).
49-
fn prove_setup<P>(r1cs: &R1CS<Base<P>>, witness: &[Base<P>]) -> ProveInputs<P>
50-
where
51-
P: FieldHash,
52-
Standard: Distribution<Ext<P>> + Distribution<Base<P>>,
53-
{
54-
let scheme = WhirR1CSScheme::<P>::new_for_r1cs(r1cs, witness.len(), 0, Vec::new(), true, HASH);
55-
ProveInputs {
56-
scheme,
57-
r1cs_owned: r1cs.clone(),
58-
witness_commit: witness.to_vec(),
59-
witness_prove: witness.to_vec(),
60-
num_witnesses: r1cs.num_witnesses(),
61-
num_constraints: r1cs.num_constraints(),
62-
}
63-
}
64-
65-
/// Time the proving core — `commit` + `prove_noir` — from pre-built inputs.
66-
fn time_prove_core<P>(
67-
inp: ProveInputs<P>,
68-
public_inputs: &PublicInputs<Base<P>>,
69-
) -> (Duration, WhirR1CSProof, WhirR1CSScheme<P>)
70-
where
71-
P: FieldHash,
72-
Standard: Distribution<Ext<P>> + Distribution<Base<P>>,
73-
{
74-
let ProveInputs {
75-
scheme,
76-
r1cs_owned,
77-
witness_commit,
78-
witness_prove,
79-
num_witnesses,
80-
num_constraints,
81-
} = inp;
82-
83-
// Transcript binding derives from the scheme; built before the timer starts.
84-
let instance = public_inputs.hash_bytes::<P>(HASH);
85-
let ds = scheme.create_domain_separator().instance(&instance);
86-
87-
let start = Instant::now();
88-
let mut merlin = ProverState::new(&ds, P::transcript_sponge(HASH));
89-
let commitment = scheme
90-
.commit(
91-
&mut merlin,
92-
num_witnesses,
93-
num_constraints,
94-
witness_commit,
95-
true,
96-
)
97-
.expect("commit");
98-
let proof = scheme
99-
.prove_noir(
100-
merlin,
101-
r1cs_owned,
102-
vec![commitment],
103-
witness_prove,
104-
public_inputs,
105-
)
106-
.expect("prove_noir");
107-
(start.elapsed(), proof, scheme)
108-
}
109-
11031
/// Build a `2^log_size`-witness squaring chain, prove, verify, and return
11132
/// `(prove, verify, narg_bytes, hints_bytes)`.
11233
fn run_one<P>(log_size: u32) -> (Duration, Duration, usize, usize)
@@ -120,7 +41,7 @@ where
12041

12142
// Only commit + prove_noir are timed (setup excluded above).
12243
let inp = prove_setup::<P>(&r1cs, &w);
123-
let (prove_t, proof, scheme) = time_prove_core::<P>(inp, &public_inputs);
44+
let (prove_t, proof, scheme) = time_prove_core::<P>(inp, &public_inputs).expect("prove");
12445

12546
let t = Instant::now();
12647
scheme
@@ -187,13 +108,14 @@ fn size_sweep() {
187108
println!();
188109
}
189110

190-
/// Repo `provekit/.claude/profile/` directory (created if absent).
111+
/// Workspace `target/profile/` directory (created if absent). Lives under the
112+
/// gitignored build dir so the artifacts are not committed and the probe is
113+
/// portable across machines.
191114
fn profile_dir() -> PathBuf {
192115
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
193116
.join("..")
194117
.join("..")
195-
.join("provekit")
196-
.join(".claude")
118+
.join("target")
197119
.join("profile");
198120
std::fs::create_dir_all(&dir).expect("create profile dir");
199121
dir
@@ -230,7 +152,8 @@ fn flamegraph_2pow20_bf() {
230152

231153
// Timed + captured: the true prove core only (setup/clones already done).
232154
let (prove_t, proof, _scheme) =
233-
time_prove_core::<provekit_backend_goldilocks::GoldilocksField>(inp, &public_inputs);
155+
time_prove_core::<provekit_backend_goldilocks::GoldilocksField>(inp, &public_inputs)
156+
.expect("prove");
234157
println!(
235158
"\n[flamegraph] 2^20 goldilocks-BF prove={prove_t:.3?} narg={} hints={}",
236159
proof.narg_string.len(),

0 commit comments

Comments
 (0)