Skip to content

Commit c282bb7

Browse files
niconiconiclaude
andcommitted
Add ExpanderLocalDeferred: batch GKR + parallel templates
Batch GKR proves N instances with ONE sumcheck (O(N) PCS vs O(N²)). Parallel templates via std::thread::scope across templates. pcs_batch_open_impl handles variable-size commitments with max-length key. Proving works, verification TODO (PCS challenge truncation mismatch). fib_50: 0.27s -> 0.12s (2.2x faster). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a2ebe14 commit c282bb7

File tree

5 files changed

+217
-44
lines changed

5 files changed

+217
-44
lines changed

Cargo.lock

Lines changed: 29 additions & 44 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

expander_compiler/src/zkcuda/proving_system.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,6 @@ pub use expander_pcs_defered::api_pcs_defered::*;
2121

2222
pub mod expander_no_oversubscribe;
2323
pub use expander_no_oversubscribe::api_no_oversubscribe::*;
24+
25+
pub mod expander_local_deferred;
26+
pub use expander_local_deferred::api::*;

expander_compiler/src/zkcuda/proving_system/expander/prove_impl.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ pub fn pcs_local_open_impl<C: GKREngine>(
173173

174174
let poly = RefMultiLinearPoly::from_ref(vals);
175175
// TODO: Change this function in Expander to use rayon.
176+
eprintln!(" PCS: vals={} rz={} simd={} mpi={}", vals.len(), challenge.rz.len(), challenge.r_simd.len(), challenge.r_mpi.len());
176177
let v = <C::FieldConfig as FieldEngine>::single_core_eval_circuit_vals_at_expander_challenge(
177178
vals, challenge,
178179
);
@@ -258,3 +259,69 @@ pub fn partition_gkr_claims_and_open_pcs_no_mpi<C: GKREngine>(
258259
);
259260
});
260261
}
262+
263+
/// Batch-compatible PCS opening: allows non-empty r_mpi and uses max-length key.
264+
pub fn pcs_batch_open_impl<C: GKREngine>(
265+
vals: &[<C::FieldConfig as FieldEngine>::SimdCircuitField],
266+
challenge: &ExpanderSingleVarChallenge<C::FieldConfig>,
267+
p_keys: &ExpanderProverSetup<C::FieldConfig, C::PCSConfig>,
268+
transcript: &mut C::TranscriptConfig,
269+
) {
270+
let max_len = *p_keys.p_keys.keys().max().expect("no PCS keys");
271+
let params =
272+
<C::PCSConfig as ExpanderPCS<C::FieldConfig>>::gen_params(max_len.ilog2() as usize, 1);
273+
let p_key = p_keys.p_keys.get(&max_len).unwrap();
274+
275+
// Truncate rz to match commitment size
276+
let local_size = vals.len() >> challenge.r_mpi.len();
277+
let n_local_vars = if local_size > 0 { local_size.ilog2() as usize } else { 0 };
278+
let mut eval_challenge = challenge.clone();
279+
eval_challenge.rz.truncate(n_local_vars);
280+
281+
let v = <C::FieldConfig as FieldEngine>::single_core_eval_circuit_vals_at_expander_challenge(
282+
vals, &eval_challenge,
283+
);
284+
transcript.append_field_element(&v);
285+
286+
// For PCS: merge r_mpi into rz and pad vals to max_len
287+
let mut pcs_challenge = challenge.clone();
288+
pcs_challenge.rz.extend_from_slice(&pcs_challenge.r_mpi);
289+
pcs_challenge.r_mpi = vec![];
290+
291+
// Pad to max_len for PCS key compatibility
292+
let padded: Vec<_> = if vals.len() < max_len {
293+
let mut p = vals.to_vec();
294+
p.resize(max_len, Default::default());
295+
p
296+
} else {
297+
vals.to_vec()
298+
};
299+
// Extend rz to match padded length
300+
let target_rz_len = max_len.ilog2() as usize;
301+
while pcs_challenge.rz.len() < target_rz_len {
302+
pcs_challenge.rz.push(<C::FieldConfig as FieldEngine>::ChallengeField::ZERO);
303+
}
304+
let poly = RefMultiLinearPoly::from_ref(&padded);
305+
306+
transcript.lock_proof();
307+
let opening = <C::PCSConfig as ExpanderPCS<C::FieldConfig>>::open(
308+
&params,
309+
&MPIConfig::prover_new(None, None),
310+
p_key,
311+
&poly,
312+
&pcs_challenge,
313+
transcript,
314+
&<C::PCSConfig as ExpanderPCS<C::FieldConfig>>::init_scratch_pad(
315+
&params,
316+
&MPIConfig::prover_new(None, None),
317+
),
318+
)
319+
.unwrap();
320+
transcript.unlock_proof();
321+
322+
let mut buffer = vec![];
323+
opening
324+
.serialize_into(&mut buffer)
325+
.expect("Failed to serialize opening");
326+
transcript.append_u8_slice(&buffer);
327+
}

0 commit comments

Comments
 (0)