Skip to content

Commit 62c9c6b

Browse files
authored
Merge pull request #222 from anoma/xuyang/update_compliance_bench
update compliance bench
2 parents 7fdb463 + 0752ed3 commit 62c9c6b

2 files changed

Lines changed: 92 additions & 53 deletions

File tree

arm_circuits/compliance/README.md

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,45 @@
11
# Compliance Circuit
22

33
This crate contains the RISC Zero compliance circuit and its local benchmark.
4-
Succinct STARK proofs are generated in the benchmark by default.
4+
Succinct STARK proofs are generated by default.
55

6-
## Benchmark
6+
## Benchmark Cases
77

8-
Run the local proving benchmark with:
8+
| Case | Description |
9+
|------|-------------|
10+
| `empty_table` | No precomputed kind table — circuit falls back to `hash_to_curve` per proof |
11+
| `file_table` | Loads `kind_table.json` — resolves kind points via table lookup |
12+
13+
The trailing number in each benchmark name is the **batch size** (number of consumed/created resources).
14+
15+
## Running
16+
17+
Local (CPU):
918

1019
```bash
1120
cargo bench --features prove --bench compliance
1221
```
1322

14-
Run the CUDA prover benchmark with:
23+
CUDA:
1524

1625
```bash
1726
cargo bench --features prove,cuda --bench compliance
1827
```
1928

20-
For faster iteration without real proof generation:
29+
Fast iteration without real proof generation:
2130

2231
```bash
2332
RISC0_DEV_MODE=1 cargo bench --features prove -p compliance
2433
```
2534

26-
The benchmark measures two cases:
35+
## Results
2736

28-
- `compliance/empty_table/prove`: no precomputed kind table, so the circuit falls back to `hash_to_curve`.
29-
- `compliance/file_table/prove`: loads `kind_table.json` and resolves kind points via table lookup.
37+
| num | `empty_table` | `file_table` |
38+
|------:|:-------------:|:------------:|
39+
| 1 | 2.125 s | 808.7 ms |
40+
| 2 | 3.084 s | 844.7 ms |
41+
| 4 | 6.320 s | 1.235 s |
42+
| 8 | 11.464 s | 1.288 s |
3043

31-
## Current Results
32-
33-
```text
34-
compliance/empty_table/prove
35-
time: [2.7347 s 2.7424 s 2.7538 s]
36-
compliance/file_table/prove
37-
time: [1.0015 s 1.0039 s 1.0067 s]
38-
```
44+
`file_table` amortizes the `hash_to_curve` cost across the batch, keeping proving
45+
time nearly flat up to 8 proofs. `empty_table` scales roughly linearly with batch size.

arm_circuits/compliance/benches/prove.rs

Lines changed: 69 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
/// - `file_table`: kind points are pre-computed in `kind_table.json`; the circuit
66
/// resolves them via a table lookup, skipping `hash_to_curve`.
77
///
8+
/// Each configuration is benchmarked with 1, 2, 4, and 8 consumed/created
9+
/// resources to measure scaling behaviour.
10+
///
811
/// # Running on CPU
912
///
1013
/// ```sh
@@ -27,14 +30,15 @@
2730
/// ```
2831
use anoma_rm_risc0::compliance::{ComplianceWitness, INITIAL_ROOT};
2932
use anoma_rm_risc0::constants::{global_kind_table, init_kind_table_from_file};
30-
use anoma_rm_risc0::merkle_path::MerklePath;
3133
use anoma_rm_risc0::nullifier_key::NullifierKey;
32-
use anoma_rm_risc0::resource::Resource;
34+
use anoma_rm_risc0::resource::{ConsumedResourceWitness, Resource};
3335
use compliance_methods::COMPLIANCE_GUEST_ELF;
34-
use criterion::{criterion_group, criterion_main, Criterion};
36+
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
3537
use risc0_zkvm::{default_prover, Digest, ExecutorEnv, ProverOpts};
3638
use std::time::Duration;
3739

40+
const RESOURCE_COUNTS: &[usize] = &[1, 2, 4, 8];
41+
3842
fn do_prove(witness: &ComplianceWitness) {
3943
let env = ExecutorEnv::builder()
4044
.write(witness)
@@ -46,32 +50,54 @@ fn do_prove(witness: &ComplianceWitness) {
4650
.unwrap();
4751
}
4852

49-
/// Build a compliance witness whose resources use `logic_ref` and `label_ref`,
50-
/// pulling the current global kind table into the witness.
51-
fn make_witness(logic_ref: Digest, label_ref: Digest) -> ComplianceWitness {
53+
/// Build a compliance witness with `count` consumed/created resources.
54+
/// Each consumed resource gets a unique nonce via its index in the last byte.
55+
fn make_witness(logic_ref: Digest, label_ref: Digest, count: usize) -> ComplianceWitness {
5256
let nf_key = NullifierKey::default();
53-
let consumed_resource = Resource {
54-
logic_ref,
55-
label_ref,
56-
quantity: 1,
57-
value_ref: Digest::default(),
58-
is_ephemeral: false,
59-
nonce: [0u8; 32],
60-
nk_commitment: nf_key.commit(),
61-
rand_seed: [0u8; 32],
62-
};
63-
let nf = consumed_resource.nullifier(&nf_key).unwrap();
64-
let created_resource = Resource {
65-
nonce: nf.as_bytes().try_into().unwrap(),
66-
..consumed_resource
67-
};
57+
58+
let consumed_data: Vec<ConsumedResourceWitness> = (0..count)
59+
.map(|i| {
60+
let mut nonce = [0u8; 32];
61+
nonce[31] = i as u8;
62+
let resource = Resource {
63+
logic_ref,
64+
label_ref,
65+
quantity: 1,
66+
value_ref: Digest::default(),
67+
is_ephemeral: false,
68+
nonce,
69+
nk_commitment: nf_key.commit(),
70+
rand_seed: [0u8; 32],
71+
};
72+
ConsumedResourceWitness::from_resource(resource, nf_key.clone())
73+
})
74+
.collect();
75+
76+
let nullifiers: Vec<Digest> = consumed_data
77+
.iter()
78+
.map(|w| w.resource.nullifier(&nf_key).unwrap())
79+
.collect();
80+
81+
let created_resources: Vec<Resource> = (0..count)
82+
.map(|i| {
83+
let nonce = Resource::derive_nonce_from_nullifiers(i as u32, &nullifiers).unwrap();
84+
Resource {
85+
logic_ref,
86+
label_ref,
87+
quantity: 1,
88+
value_ref: Digest::default(),
89+
is_ephemeral: false,
90+
nonce,
91+
nk_commitment: nf_key.commit(),
92+
rand_seed: [0u8; 32],
93+
}
94+
})
95+
.collect();
96+
6897
ComplianceWitness {
69-
consumed_resource,
70-
merkle_path: MerklePath::default(),
98+
consumed_data,
99+
created_resources,
71100
ephemeral_root: *INITIAL_ROOT,
72-
nf_key,
73-
created_resource,
74-
// Scalar::ONE encoded as big-endian 32 bytes
75101
rcv: [vec![0u8; 31], vec![1u8]].concat(),
76102
kind_table: global_kind_table().to_vec(),
77103
}
@@ -84,17 +110,21 @@ fn bench_empty_table(c: &mut Criterion) {
84110
group.warm_up_time(Duration::from_secs(1));
85111
group.measurement_time(Duration::from_secs(1));
86112

87-
group.bench_function("prove", |b| {
88-
b.iter_with_setup(ComplianceWitness::default, |witness| do_prove(&witness));
89-
});
113+
for &count in RESOURCE_COUNTS {
114+
group.bench_with_input(BenchmarkId::new("prove", count), &count, |b, &count| {
115+
b.iter_with_setup(
116+
|| make_witness(Digest::default(), Digest::default(), count),
117+
|witness| do_prove(&witness),
118+
);
119+
});
120+
}
90121

91122
group.finish();
92123
}
93124

94125
/// Precomputed kind table from file: the circuit resolves kind points via table
95126
/// lookup, skipping `hash_to_curve`. The witness uses the `logic_ref` and
96-
/// `label_ref` of the first entry in the loaded table so both consumed and
97-
/// created resources are guaranteed to hit the table.
127+
/// `label_ref` of the first entry in the loaded table so all resources hit the table.
98128
fn bench_file_table(c: &mut Criterion) {
99129
let kind_table_path =
100130
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../arm/kind_table.json");
@@ -109,12 +139,14 @@ fn bench_file_table(c: &mut Criterion) {
109139
group.warm_up_time(Duration::from_secs(1));
110140
group.measurement_time(Duration::from_secs(1));
111141

112-
group.bench_function("prove", |b| {
113-
b.iter_with_setup(
114-
|| make_witness(logic_ref, label_ref),
115-
|witness| do_prove(&witness),
116-
);
117-
});
142+
for &count in RESOURCE_COUNTS {
143+
group.bench_with_input(BenchmarkId::new("prove", count), &count, |b, &count| {
144+
b.iter_with_setup(
145+
|| make_witness(logic_ref, label_ref, count),
146+
|witness| do_prove(&witness),
147+
);
148+
});
149+
}
118150

119151
group.finish();
120152
}

0 commit comments

Comments
 (0)