-
Notifications
You must be signed in to change notification settings - Fork 40
feat: multiple instances of a circuit in single shard #1126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
.github/workflows/integration.yml
Outdated
| - name: Install m4 | ||
| run: sudo apt-get install -y m4 | ||
|
|
||
| - name: Run fibonacci (release) in 3 shards |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having 3 instances of ShardRam circuit in one shard.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR enables proving multiple instances of a circuit within a single shard, addressing memory constraints when handling large numbers of records (e.g., ~2M records in shard 0 of ShardRam circuit that exceed GPU memory capacity).
Key changes:
- Introduced
ChipInputstructure to manage multiple circuit instances per chip - Refactored witness management to support chunking of circuit instances
- Added configurable cross-shard access limits via
CENO_CROSS_SHARD_LIMITenvironment variable
Reviewed Changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| ceno_zkvm/src/tables/shard_ram.rs | Modified witness input type and EC sum extraction to return SepticPoint instead of vector |
| ceno_zkvm/src/structs.rs | Introduced ChipInput struct and refactored witness storage to support multiple circuit instances |
| ceno_zkvm/src/stats.rs | Updated to extract num_instances from new ChipInput structure |
| ceno_zkvm/src/scheme/verifier.rs | Updated chip proof iteration and commented out EC sum verification logic |
| ceno_zkvm/src/scheme/tests.rs | Adapted test to access witness via new ChipInput structure |
| ceno_zkvm/src/scheme/prover.rs | Refactored to use Vec instead of BTreeMap for chip proofs and iterate over chip inputs |
| ceno_zkvm/src/scheme/mock_prover.rs | Updated to iterate over witnesses using new iter_sorted method |
| ceno_zkvm/src/scheme.rs | Changed chip_proofs from BTreeMap to Vec to support multiple instances |
| ceno_zkvm/src/keygen.rs | Added circuit name to index mapping initialization |
| ceno_zkvm/src/instructions.rs | Added debug logging for record assignment |
| ceno_zkvm/src/e2e.rs | Added cross-shard access limit configuration and updated witness aggregation logic |
| .github/workflows/integration.yml | Added integration test with limited cross-shard accesses |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| type TableConfig = ShardRamConfig<E>; | ||
| type FixedInput = (); | ||
| type WitnessInput = Vec<ShardRamInput<E>>; | ||
| type WitnessInput = [ShardRamInput<E>]; |
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changing WitnessInput from Vec<ShardRamInput<E>> to [ShardRamInput<E>] (unsized slice type) is a breaking API change that will prevent using this type directly in many contexts. Slices cannot be used as concrete types for struct fields or return values. Consider using &[ShardRamInput<E>] if you need a slice reference, or keep Vec<ShardRamInput<E>> if ownership is required.
| type WitnessInput = [ShardRamInput<E>]; | |
| type WitnessInput = Vec<ShardRamInput<E>>; |
| // expected_septic_xy[0..SEPTIC_EXTENSION_DEGREE].into(); | ||
| // let expected_septic_y: SepticExtension<E::BaseField> = | ||
| // expected_septic_xy[SEPTIC_EXTENSION_DEGREE..].into(); | ||
|
|
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical verification logic for EC sum validation has been commented out. This removes an important correctness check. Either remove the commented code if it's no longer needed, or if this is temporary for debugging/development, add a TODO comment explaining why it's disabled and when it should be re-enabled.
| // TODO: EC sum verification is temporarily disabled for debugging/development. | |
| // Re-enable the following assertions before production release to ensure proof correctness. |
| .circuit_name_to_index | ||
| .get(&circuit_name) | ||
| .cloned() | ||
| .expect("invalid circuit {} not exist in ceno zkvm"); |
Copilot
AI
Nov 13, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message string doesn't include the actual circuit name variable. Change to .expect(&format!(\"invalid circuit {} not exist in ceno zkvm\", circuit_name)) to make the error message informative.
| .expect("invalid circuit {} not exist in ceno zkvm"); | |
| .expect(&format!("invalid circuit {} not exist in ceno zkvm", circuit_name)); |
hero78119
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
first pass, overall lgtm, some stuff still pending clarify
ceno_zkvm/src/instructions.rs
Outdated
| // TODO: selector is the only structural witness | ||
| // this is workaround, as call `construct_circuit` will not initialized selector | ||
| // we can remove this one all opcode unittest migrate to call `build_gkr_iop_circuit` | ||
| tracing::debug!("assign {} records for {}", steps.len(), Self::name()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This log a bit duplicate with https://github.com/scroll-tech/ceno/blob/master/ceno_zkvm/src/instructions/riscv/rv32im.rs#L461-L465 and suggest just need keep one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
clean up this log by push new commit
ceno_zkvm/src/e2e.rs
Outdated
| let shard_ram_witnesses = zkvm_witness.get_circuit_witness(&ShardRamCircuit::<E>::name()); | ||
|
|
||
| if let Some(shard_ram_witnesses) = shard_ram_witnesses { | ||
| let shard_ram_digest: SepticPoint<E::BaseField> = shard_ram_witnesses |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick: better name shard_ram_ec_point or ec_point
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed to shard_ram_ec_sum
| } | ||
| }) | ||
| .or_else(|| { | ||
| vk.get_cs().is_static_circuit().then(|| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i am checking how new logic work after removing this. Because there are some circuit which only has fixed commitment and they will not present in witness rmm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
push new commits to remove is_static_circuit because now EVERY circuit got 1 structural_witness as selector, thus this mechanism does not needed
ceno_zkvm/src/structs.rs
Outdated
| self.num_instances | ||
| .insert( | ||
| let circuit_inputs = global_input | ||
| .chunks(shard_ctx.max_num_cross_shard_accesses) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
par_chunks better
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in new commit
ceno_zkvm/src/scheme.rs
Outdated
| pub pi_evals: Vec<E>, | ||
| pub chip_proofs: BTreeMap<usize, ZKVMChipProof<E>>, | ||
| // each circuit may have multiple proof instances | ||
| pub chip_proofs: Vec<(usize, ZKVMChipProof<E>)>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
soundness: i think we need to have some mechanism to check, w.g. maximal proofs allow for a chip. E.g. what if prover fill same proof multiple times
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made this part to be BTreeMap<usize, Vec<ZKVMChipProof<E>>> then at least constrain OMC init proof can only be ONE proof
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what if prover fill same proof multiple times
Then the offline memory checking prod_r == prod_w must fail.
| } | ||
| if num_instances.is_empty() { | ||
| // we need to drain respective fixed when num_instances is 0 | ||
| if cs.num_fixed() > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: here we drain num_fixed from mles otherwise their order will be mismatched
| .insert(OC::name(), vec![witness[0].num_instances()]) | ||
| .is_none() | ||
| let num_instances = vec![witness[0].num_instances()]; | ||
| let input = ChipInput::new( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we need to assign empty witness as placeholder to witnesses to skip respective fixed mles, see
https://github.com/scroll-tech/ceno/pull/1126/files#r2522083303
| let input = ChipInput::new( | ||
| TC::name(), | ||
| witness, | ||
| if num_instances > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| .into(), | ||
| )); | ||
| } | ||
| if shard_id == 0 && circuit_vk.get_cs().with_omc_init_only() && proofs.len() != 1 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More robust soundness: omc table can only present one proof
| && shard_id > 0 | ||
| { | ||
| PCS::write_commitment(fixed_commit, &mut transcript).map_err(ZKVMError::PCSError)?; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @darth-cy please refer new verifier logic here to support
one shard + one chip + multiple proofs + respective soundness check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code diff for my change during code review
Pending issue
- need refactor mock_proving to support multi-proofs of a chip in a shard
- remove environment loading and move to command line argument
- retain ecc check https://github.com/scroll-tech/ceno/pull/1126/files#r2523080132
|
|
||
| assert_eq!(&ecc_proof.sum.x, &expected_septic_x); | ||
| assert_eq!(&ecc_proof.sum.y, &expected_septic_y); | ||
| // let expected_septic_xy = cs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will be retain in #1130
Summary
We want to prove multiple instances of a circuit in a single shard.
For example, in the case of
ShardRamcircuit, the 0-th shard often has ~2M records which is too large to fit in GPU memory.