Skip to content

Function grouping - #551

Draft
gabriel-barrett wants to merge 3 commits into
mainfrom
group-functions
Draft

Function grouping#551
gabriel-barrett wants to merge 3 commits into
mainfrom
group-functions

Conversation

@gabriel-barrett

Copy link
Copy Markdown
Member

Circuit-level function grouping; grouped kernel and verifier partitions

Several rarely-used functions can now share ONE proving circuit instead of
each committing its own. Grouping is a circuit-level choice (no source
pragma): the bytecode, execution, and query record are untouched, and
callers still target function indices on the function channel, so claims
are partition-agnostic. A merged circuit branches on per-member function
selectors and takes the max of the members' auxiliary columns.

Mechanics

  • Selector sharing: grouped circuits share the members' internal
    selector columns (max, not sum) — every member constraint is gated by its
    function selector B_j. The selector block is
    [one B_j per member | max member selectors].
  • Degree-5 sizing: logUp chained steps are sized by lookup-argument
    degree to fill the blowup-4 budget — raw args on branchless singletons
    (k = 4), B·arg on all-branchless groups (k = 2), B·sel·arg on mixed
    groups (k = 1). The byte chips group 4 per step.
  • Trivial field arithmetic in the multi-stark Lean verifier is inlined
    (@-calls), shrinking the codegen'd kernel.

Applied partitions

Grouping trades workload FFT cost (every member row pays the merged width)
for everything priced per circuit: vk size, commitments, opening points,
and above all recursive-verifier work. Both partitions were measured — see
cold-groups/cold-grouping-report.md.

  • IxVM kernel (IxVM.coldGroups): the 669 circuits holding <0.5% FFT
    share across three kernel workloads, in 15 width bands.
    730 → 76 circuits, total width 32,201 → 6,791; the execute-stats FFT
    proxy pays ~17–27% (all 71 pins + shard aggregate re-pinned).
  • Recursive verifier (MultiStark.verifierColdGroups): three bands —
    the 12 tall-but-narrow circuits (so the multi-million-row bulk pays width
    73, not the merged 635), the remaining 184 functions, and the blake3
    compression pair as its own all-branchless k = 2 group (folding it in
    drops lookups to k = 1 and explodes stage 2). 209 → 14 circuits,
    total width 10,946 → 2,501 (−77%); on the IxVM-scale recursion the
    modeled outer-prove cost drops ~2.5× vs a naive two-band split, with the
    largest single circuit down from 7.7e11 to 1.7e11.

Several functions can now be proven by ONE circuit: the members are
walked like branches of a single function - auxiliary columns and
lookup slots are shared across members (the same save/restore sharing
match arms already use), selector columns are laid out consecutively
per member, and every member folds its selector-gated return message
(carrying its own function index) into the shared lookup slot 0
against a single shared multiplicity column. One extra constraint
enforces cross-member exclusivity: the sum of the members' top-block
selectors must be boolean. Callers are untouched - calls still target
function indices on the function channel - so grouping is invisible to
execution, the query record, and the interpreter.

Grouping is a CIRCUIT-level choice, not a property of the function
library, so there is no source annotation: Source.Toplevel.compile
builds the default singleton partition (Bytecode.Toplevel.circuits,
one circuit per constrained function - behavior-identical to before),
and CompiledToplevel.groupFunctions optionally regroups it by function
NAME (validated: known, constrained, non-entry, no duplicates). The
merged layout is max inputs, summed selectors, max auxiliaries, max
lookups - so grouping fits rarely-called functions of similar shape:
each (rare) row pays the group's selector count while the system sheds
one circuit (vk entry, commitment matrix, verifier work) per absorbed
member.

Rust consumes the partition directly (bytecode Circuit via FFI;
constraints/trace/synthesis iterate circuits, witness rows concatenate
the members' queried rows in member order). The stage-2 lookup group
size and the branchless raw-argument rule now key on the CIRCUIT
layout: multi-member circuits are branching by construction, so their
arguments are selector-superposed exactly like match arms.

Tests: the aiur suite proves the same toplevel twice - ungrouped and
with a 3-member test group (different arities, matches, cross-member
call, recursion) - plus structural checks on the partition (members,
merge-rule layout, every constrained function in exactly one circuit).
All suites pass unchanged (ixvm FFT pins identical - the default
partition is behavior-neutral); codegen is unaffected (execution
ignores the partition).
…ions)

Route every site that compiles the IxVM kernel or the recursive-verifier
toplevel for proving/verifying through `compileWithGroups` with a
per-toplevel grouping datum (`IxVM.coldGroups`,
`MultiStark.verifierColdGroups`) - CLI check/prove/verify, the ixvm test
runner, the recursive-verifier tests, and the benches. `groupFunctions`
resolves members by STRING name (the exact `toString` of the Global, the
inverse of what statistics print, so measured groupings feed back
verbatim). Both partitions start EMPTY, i.e. singleton circuits -
behavior-identical to before; the data files are the single knob later
commits turn.
Add Sel/Aux/Lkp columns (the circuit layout's selectors, auxiliaries,
lookups; zero for memory/gadget rows) to the per-circuit statistics
table. Grouping instrumentation only: merging is cheapest between
circuits whose auxiliaries and lookups are CLOSE (both merge by max,
selectors sum), so these columns are what a partition builder needs
next to the width. Meant to be reverted once the partitions are chosen
- this commit is self-contained in Ix/Aiur/Statistics.lean.
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.

1 participant