Skip to content

Conversation

@kunxian-xia
Copy link
Collaborator

@kunxian-xia kunxian-xia commented Nov 10, 2025

Summary

We want to prove multiple instances of a circuit in a single shard.

For example, in the case of ShardRam circuit, the 0-th shard often has ~2M records which is too large to fit in GPU memory.

- name: Install m4
run: sudo apt-get install -y m4

- name: Run fibonacci (release) in 3 shards
Copy link
Collaborator Author

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.

@kunxian-xia kunxian-xia requested review from Copilot and hero78119 and removed request for hero78119 November 12, 2025 15:14
Copy link

Copilot AI left a 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 ChipInput structure 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_LIMIT environment 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>];
Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
type WitnessInput = [ShardRamInput<E>];
type WitnessInput = Vec<ShardRamInput<E>>;

Copilot uses AI. Check for mistakes.
// expected_septic_xy[0..SEPTIC_EXTENSION_DEGREE].into();
// let expected_septic_y: SepticExtension<E::BaseField> =
// expected_septic_xy[SEPTIC_EXTENSION_DEGREE..].into();

Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
// TODO: EC sum verification is temporarily disabled for debugging/development.
// Re-enable the following assertions before production release to ensure proof correctness.

Copilot uses AI. Check for mistakes.
.circuit_name_to_index
.get(&circuit_name)
.cloned()
.expect("invalid circuit {} not exist in ceno zkvm");
Copy link

Copilot AI Nov 13, 2025

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.

Suggested change
.expect("invalid circuit {} not exist in ceno zkvm");
.expect(&format!("invalid circuit {} not exist in ceno zkvm", circuit_name));

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

@hero78119 hero78119 left a 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

// 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());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

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

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
Copy link
Collaborator

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

Copy link
Collaborator

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(|| {
Copy link
Collaborator

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

Copy link
Collaborator

@hero78119 hero78119 Nov 13, 2025

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

self.num_instances
.insert(
let circuit_inputs = global_input
.chunks(shard_ctx.max_num_cross_shard_accesses)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

par_chunks better

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in new commit

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>)>,
Copy link
Collaborator

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

Copy link
Collaborator

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

Copy link
Collaborator Author

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 {
Copy link
Collaborator

@hero78119 hero78119 Nov 13, 2025

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(
Copy link
Collaborator

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 {
Copy link
Collaborator

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 {
Copy link
Collaborator

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)?;
}
Copy link
Collaborator

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

Copy link
Collaborator

@hero78119 hero78119 left a 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

05c7504...adadd5b

Pending issue

  1. need refactor mock_proving to support multi-proofs of a chip in a shard
  2. remove environment loading and move to command line argument
  3. 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
Copy link
Collaborator

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

@hero78119 hero78119 enabled auto-merge November 13, 2025 11:22
@hero78119 hero78119 added this pull request to the merge queue Nov 13, 2025
Merged via the queue into master with commit 07fc421 Nov 13, 2025
4 checks passed
@hero78119 hero78119 deleted the feat/multiple_instances branch November 13, 2025 11:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants