Skip to content

GKR: lookup tables and bucketing - #146

Open
xrvdg wants to merge 25 commits into
mainfrom
xr/lut
Open

xrvdg wants to merge 25 commits into
mainfrom
xr/lut

Conversation

@xrvdg

@xrvdg xrvdg commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

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

xrvdg added 14 commits October 8, 2026 15:54
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.
@xrvdg
xrvdg marked this pull request as draft October 22, 2026 04:22
@xrvdg
xrvdg changed the base branch from main to xr/bittable October 22, 2026 04:23
@xrvdg
xrvdg added this pull request to stack #147 October 22, 2026 04:24
@xrvdg
xrvdg marked this pull request as ready for review October 22, 2026 04:55
@xrvdg
xrvdg requested a review from zkfriendly October 22, 2026 04:55
@xrvdg xrvdg mentioned this pull request Oct 22, 2026
21 tasks
@xrvdg xrvdg changed the title Xr/lut GKR: lookup tables and bucketing Oct 22, 2026
Base automatically changed from xr/bittable to main October 22, 2026 05:18

@zkfriendly zkfriendly left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

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 {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why was this made public? no external callers

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: we could move this allocation to after dimension checks so we don't allocate first and then reject it

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

It's very unlikely for the dimension check to fail, and this way the match clauses themselves are cleaner.

Comment thread crates/gkr/src/lib.rs Resolved
@xrvdg
xrvdg requested a review from zkfriendly November 13, 2026 08:58
@xrvdg

xrvdg commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator Author

The remark about log_bits prompted me to search for more simplifications, so I've added those in.

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.

2 participants