Logup optimization - #65
Draft
gabriel-barrett wants to merge 2 commits into
Draft
Conversation
gabriel-barrett
marked this pull request as draft
July 23, 2026 17:15
gabriel-barrett
force-pushed
the
logup-optimization
branch
4 times, most recently
from
July 28, 2026 11:58
963f7ff to
8c9965e
Compare
Circuits are now data, not trait impls: frontend Expr/ExtExpr trees compile to a flat, hash-consed, base-only node graph (graph::ConstraintGraph) that the prover, verifier and witness generator all evaluate with a dense forward sweep. Extension constraints are coordinate-expanded at compile time (Karatsuba for D=2, schoolbook otherwise), so the extension degree never appears in the compiled artifact; constraint roots are canonicalized (const-zero dropped, const-nonzero rejected at setup, sorted and deduplicated). - expr.rs: frontend expression trees and the internal CircuitSpec - graph.rs: interning compiler producing ConstraintGraph - eval.rs: dense sweep evaluator shared by prover, verifier and the lookup witness (plus a recursive reference evaluator for tests) - lookup.rs: logUp stage-2 constraints synthesized as ordinary ExtExpr data and compiled like user constraints; Lookup keeps its push/pull constructors - system.rs: System over CircuitInputs (main width, preprocessed trace, constraints, lookups); stage-2 and public-input layout is derived at setup, not authored - p3_adapter.rs: Plonky3-style authoring preserved. An AirBuilder implementation records Air::eval into Expr trees; LookupAir pairs an AIR with its lookups and converts into CircuitInputs; System::new accepts anything Into<CircuitInputs>. Existing AIR-based tests, examples and benches run unchanged except for import paths and the System type losing its AIR parameter. - builder/ (symbolic expressions, constraint folders, check builder) deleted; prover/verifier keep the same protocol and transcript
Replace the committed-inverse logUp with a chained partial-accumulator argument: the only stage-2 columns are the running accumulator and one partial-accumulator column per lookup after the first — no message inverse is ever committed (stage-2 width L·D instead of (1+L)·D). Each lookup `(m, M)` contributes one chain step `M·(p − p_prev) = m`, so `p − p_prev = m/M`, threaded from this row's accumulator column into the next row's with a cyclic boundary correction `is_last·(next_acc − acc)` — the step constraint is uniform across all rows, needing no is_transition selector, and the accumulator's absolute level is gauge-free (only its cyclic delta is pinned to the public claim). The witness fills the accumulator with the direct fractional sum `m·M⁻¹`: one batch inversion of the messages, per-lookup deltas computed in parallel, and an additions-only prefix sum on the sequential path.
gabriel-barrett
force-pushed
the
logup-optimization
branch
from
July 28, 2026 23:20
8c9965e to
b80c348
Compare
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 optimization: chained partial accumulators + message grouping
Shrinks the lookup argument's committed stage-2 trace from
1 + Ltomax(1, ⌈L/k⌉)extension-field columns per circuit (L= lookups,k= anew system-level lookup group size parameter), by replacing the per-lookup
inverse columns with a chain of partial accumulators whose steps each cover a
group of
kmessages.The scheme
The old layout committed, per row, the running accumulator plus one inverse
column
wᵢ = Mᵢ⁻¹per lookup, constrained bywᵢ·Mᵢ = 1and a separateaccumulator-update constraint.
The new layout threads the accumulator through the row: the committed
columns are the running accumulator
accplus one partial accumulator perlookup group after the first, and each group
(m₁, M₁), ..., (m_k, M_k)contributes a single constraint
which forces the chain step
p − p_prevto be exactlyΣᵢ mᵢ/Mᵢ— thedenominators clear via
m₁/M₁ + m₂/M₂ = (m₁M₂ + m₂M₁)/(M₁M₂), generalized tokterms — without ever committing a message inverse. The final step of eachrow lands on the next row's accumulator through the cyclic rotation.
Cyclic boundary
Instead of gating the final step behind
is_transitionand adding a separatelast-row constraint (which would multiply the message product by the
IsLastRowselector and cost an extra degree), the final step carries apublic correction term and holds on all rows:
On ordinary rows the correction vanishes. On the last row the rotation wraps
to
acc(0), and the telescoped sum around the cycle states exactlynext_acc = acc + Σ mᵢ/Mᵢ. Two consequences:offset, with no first-row pin — since only the public difference
next_acc − accis enforced. (The witness still starts it ataccforclarity.)
every chain step has the same degree:
max(1 + Σᵢ deg(Mᵢ), maxᵢ(deg(mᵢ) + Σⱼ≠ᵢ deg(Mⱼ))). No lookup constraintis gated by a row selector, and the degree profile is flat across rows and
groups.
This requires the first/last-row selectors handed to the constraint folders
to take value exactly 1 on their row. The PCS returns them unnormalized
(
l_first(1) = n,l_last(g^{n−1}) = n·g), which is fine for gating but notfor additive use, so the prover and verifier now rescale them by the matching
constants right after computing them. Scaling gated constraints by a nonzero
constant is semantics-preserving; the debug builder already used 0/1
selectors, so all builders now agree on one convention.
Degree accounting
With degree-1 multiplicities and degree-
dmessages, a group ofklookupscosts degree
1 + k·d(typically dominated by the product side). Examples:API
System::new_with_lookup_group_size(config, airs, k)— the newconstructor; the group size is applied uniformly to every circuit
(consecutive lookups, final group smaller when
k ∤ L).System::new(config, airs)is unchanged and meansk = 1.assertion now fires for a too-aggressive grouping, so a bad
kpanics inthe constructor — before any proving — with
"...; increase log_blowup, lower the constraint degree, or reduce the lookup group size", instead of surfacing later as an out-of-domainevaluation mismatch.
LookupAir::from_parts(inner_air, lookups, preprocessed, lookup_group_size)— reassembles an air without recomputing preprocessed traces, for
downstream verifying-key codecs (the group size is now a private field of
LookupAir, set by theSystemconstructor).LookupValues::stage_2_tracestakes the per-circuit group sizes; theprover's batch inversion shrinks to one inverse per group (the witness
values are unchanged in meaning — the partial accumulators are the running
sums the old scheme computed anyway).
Soundness
telescoping, same
N/|F_ext|Schwartz–Zippel term, and the layout is boundinto the transcript through
observe_shape(stage-2 widths, constraintcounts and degrees).
Mᵢ = 0— the one case where a chain constraint would leave its stepunconstrained — occurs with probability ≤
N/|F_ext|over the challenges,since messages are committed before β, γ are sampled. This term is already
in the union bound; previously the same event was a completeness failure
(
w·M = 1unsatisfiable).the protocol only as public values, and only differences are constrained.
Cost
Per circuit, stage 2 drops from
1 + Lto⌈L/k⌉extension columns — atk = 2, less than half the stage-2 FFT/Merkle/opening work — and theLproduct-check constraints disappear (one chain constraint per group instead).
The quotient degree is unchanged wherever the grouped step degree does not
exceed the circuit's existing maximum. Downstream (Aiur, all messages
degree 2 at blowup 4 with
k = 2), the modeled per-circuit FFT cost drops byroughly 25–30%.
Breaking changes
(different stage-2 layout and constraint set).
LookupAir's fields are no longer fully public (construct vianeworfrom_parts);LookupValues::stage_2_tracestakes group sizes.with it (see
from_parts).Testing
new scheme.
circuits in one system, stage-2 trace reduced to the accumulator column
alone), a within-circuit remainder group, and a
#[should_panic]testpinning the setup-time rejection message.
(70 prove/verify cases), BLAKE3 and SHA-256 circuits, and the IxVM kernel
checks, all green at
k = 2, blowup 4.