Repository navigation
Conversation
…, does not build)
…yers 0 to 2 for the sumcheck
…op batched_eval's groups argument
… prove_layer and batched_eval into helpers
gpgkr_prove takes the LayerWitnesses. The round loop moves out of prove_layer into prove_rounds, so a table-backed round 1 can hand its bound halves to the dense rounds through round_one: Option<Field>. The gkr tests build bit tables at (7, 15), the smallest shape the gates accept, and check the prover against the leaves built one bit at a time.
Layers 2, 1 and the leaves are no longer materialised. Round 1 of each reads the bit matrix and the 16-entry lookup tables through LutLayers and writes only the children it has bound, [left′ | right′], into one allocation sized for the leaves'. prove_rounds takes over from round 2. The leaves and layer 1 sum round 1's eq weights in buckets, with eq split into eq_hi · eq_lo; layer 2 works node by node. The sent sums are named after their points: sum_b at the boolean point b, sum_inf at ∞. gkr bench at (10, 15): 116.2 -> 71.5 ms on all threads, 215.7 -> 166-168 ms on one.
Flattened in pairs and zipped per position, every round-1 loop over the lookup tables compiled to a per-position state machine: a nibble counter, a branch and a variable shift per position, and for layer 2 the iterator state on the stack. pairs now yields one item per word, and each user zips the words with 16-position chunks of its own data and loops inside: the leaf sums unroll, the others become counted loops. gkr bench at (10, 15), one thread: 124.6e9 -> 116.8e9 instructions, 168 -> 159.5 ms fastest; all threads: 71 -> 70.3 ms fastest.
LayerWitnesses holds the LutLayers that batched_eval builds, and layer3 reads the lookup tables through LutLayers::pieces and pairs, as round 1 of layer 1 does. lower_luts, layer3 and fold_layers run on rayon with the same PARALLEL_MIN_LANES threshold as the sumcheck. LutLayers::new now asserts that each lookup table's run holds at least a word of nibbles, which layer3 used to check. fold_into is gone. gkr bench at (10, 15), all threads: 70.35 -> 66.1-66.6 ms fastest; one thread unchanged (159.4 ms, 116.8e9 instructions).
Nothing reads layers 0 to 2 in full any more, so lower_layers, IntoIterator for LayerWitnesses and lut_nodes are gone. lut_layers_match_the_leaf_construction compares the output and layer 3 and up; lut_layers_prove_from_any_top_coordinate covers layers 0 to 2 through the sumchecks that read them. The docs of LayerWitnesses, gpgkr_prove's witnesses, EqTables::new and alloc_storage say what they hold now, and the layout comment in GrandProductCircuit::new and the test comments speak of trees, node positions and nibbles instead of rows and columns. gkr bench at (10, 15) unchanged: 116.8e9 instructions on one thread.
zkfriendly
left a comment
There was a problem hiding this comment.
PR look good. I compared fc1e552 with 02de728` on an Apple M4 Max, using Rust 1.97.1 release builds. I used ten measured samples per case, identical inputs, warmups, and alternating revisions and was able to produce these results:
| Shape | 1 thread | Speedup | 8 threads | Speedup |
|---|---|---|---|---|
| (14, 8) | 41.6 → 20.1 | 2.07× | 29.5 → 8.0 | 3.69× |
| (15, 7) | 41.0 → 19.0 | 2.16× | 30.7 → 8.6 | 3.55× |
| (15, 9) | 162.1 → 74.3 | 2.18× | 112.2 → 25.0 | 4.49× |
| (10, 15) | 329.3 → 145.3 | 2.27× | 222.5 → 45.9 | 4.85× |
| (16, 10) | 783.2 → 286.0 | 2.74× | 442.3 → 86.5 | 5.12× |
Also the proofs verify correctly using the base verifier!
left some nit comments!
| const MASK: Word = (1 as Word).unbounded_shl(S as u32).wrapping_sub(1); | ||
| fn new(iter: Iter<'a, Word>) -> Self { | ||
| const MASK: u64 = 1u64.unbounded_shl(S as u32).wrapping_sub(1); | ||
| pub fn new(iter: Iter<'a, Word>) -> Self { |
There was a problem hiding this comment.
why was this made public? no external callers
There was a problem hiding this comment.
I don't have a strong opinion either way, so I've removed it. This was originally planned to be used in more places.
| let dim2_words = dim2 / BITS; | ||
|
|
||
| let mut out = bytemuck::zeroed_vec(xs.len()); | ||
| let mut block = [0; BITS]; |
There was a problem hiding this comment.
nit: we could move this allocation to after dimension checks so we don't allocate first and then reject it
There was a problem hiding this comment.
It's very unlikely for the dimension check to fail, and this way the match clauses themselves are cleaner.
…gpgkr_verify's layers as usize
… a coordinate per tree bit
… SMALLEST from PACK_BITS and MIN_LOG_BITS
…t GKR's s >= 4 floor
…as loops, drop EqTables' empty-point case, document gpgkr_verify, tidy the tests
…::new rather than GrandProductCircuit::new
…the prover's init_circuit
|
The remark about log_bits prompted me to search for more simplifications, so I've added those in. |
Implements lookup tables, bucketing and an efficient way to create the nibbles needed for lookup. This only targets the first round. Later PRs will focus on pushing this optimisations through more rounds of sumcheck.
Partially implements
eq_hi · eq_lo.EqTablesis still dense.