logUp grouping - #70
Open
gabriel-barrett wants to merge 1 commit into
Open
Conversation
Add `CircuitInputs::lookup_group_size` (default 1): one chained
accumulator step may consume k messages at once,
(prod_j m_j) * (acc' - acc) - sum_j mult_j * prod_{j'!=j} m_{j'} = 0
which forces acc' = acc + sum_j mult_j/m_j exactly as k ungrouped steps
would, but with ceil(L/k) committed accumulator slots instead of L - at
the cost of raising the logUp constraint degree to about
sum_j deg(m_j) + 1. The option is per circuit because the degree budget
depends on each circuit's message degrees; it is bounded by
MAX_LOOKUP_GROUP (8) so the evaluator's group scratch stays on the
stack, and observed into the transcript shape since it changes the
constraint structure.
- synthesize_lookups (the pinned spec) and logup_constraint_values (the
direct evaluator) chunk lookups by the group size; group products use
prefix/suffix arrays so each prod_{j'!=j} costs one multiplication.
The singleton-group path is special-cased to the exact pre-grouping
arithmetic, so the default k = 1 costs what it did before.
- stage_2_traces pushes one accumulator per GROUP per row (still batch
inversion + a single linear pass, one multiply-add per lookup).
- logup_max_degree computes the grouped degree analytically per chunk:
max(sum deg(m_j) + 1, max_j (deg(mult_j) + sum_{j'!=j} deg(m_{j'}))).
- The pin test runs group sizes 1, 2, 3 over 3 lookups (covering an
uneven tail); the u32_add e2e test groups its 13 degree-1 messages in
pairs while the byte table stays ungrouped, covering mixed group
sizes in one system.
A/B bench (lookup-heavy circuit, 16 lookups with degree-2 messages,
2^14 rows, vs a6f5441): default k = 1 at parity (quotient constraint
eval ~19ms, stage-2 build ~9-14ms on both); k = 2 halves the stage-2
accumulator columns and doubles quotient eval (~38ms) because degree
3 -> 5 doubles the quotient domain - the intended per-circuit tradeoff.
gabriel-barrett
force-pushed
the
logup-grouping
branch
from
July 31, 2026 16:38
07dc89d to
e2c1344
Compare
gabriel-barrett
marked this pull request as ready for review
July 31, 2026 16:39
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
logUp: circuit-local message grouping
Adds
CircuitInputs::lookup_group_size(default 1): one chainedaccumulator step may consume k consecutive messages instead of one,
committing
⌈L/k⌉stage-2 accumulator slots instead ofL— at thecost of a higher logUp constraint degree. The option is per circuit,
not global: whether grouping fits depends on each circuit's message
degrees and its remaining degree budget.
How it works
The ungrouped chained step forces
acc' = acc + mult/mviam·(acc' − acc) − mult = 0. For a groupm_0, …, m_{s−1}thesum-of-fractions identity is multiplied through by the group's message
product:
which forces
acc' = acc + Σ_j mult_j/m_jexactly assungroupedsteps would, but with one committed accumulator slot instead of
s. Lookups are chunked in order; the last group may be smaller. Thefinal group's step targets the next row's slot 0 plus the boundary
injection
is_last_row·Δ, so the cyclic-telescoping argument isunchanged from the chained scheme.
The degree per group is roughly
Σ_j deg(m_j) + 1(preciselymax(Σ deg(m_j) + 1, max_j [deg(mult_j) + Σ_{j'≠j} deg(m_{j'})]),computed analytically by
logup_max_degree). E.g. two degree-1messages group at degree 3; degree-2 messages usually can't afford
grouping — hence the circuit-local dial.
Implementation
synthesize_lookups(the pinned executable spec) andlogup_constraint_values(the direct evaluator used by prover andverifier) chunk lookups by the group size. Group products use
prefix/suffix arrays so each
Π_{j'≠j}costs one multiplication;scratch stays on the stack, bounded by
MAX_LOOKUP_GROUP = 8.pre-grouping arithmetic — the default
k = 1pays nothing for thefeature.
stage_2_tracespushes one accumulator per group per row; still asingle batch inversion plus one linear pass, one multiply-add per
(row, lookup).
(
observe_shape): it changes the constraint structure, whichcounts and degrees alone don't pin down.
lookup_group_size ≤ MAX_LOOKUP_GROUPand the usualquotient-degree bound fails loudly for a too-aggressive grouping.