Skip to content

Function grouping - #551

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

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

Conversation

@gabriel-barrett

@gabriel-barrett gabriel-barrett commented Aug 12, 2026

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.

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.
Rebuilt from fresh String.split / Array.extract_append kernel-check
profiles on the post-xor-split circuits (the pre-rebase groupings were
discarded as obsolete). Same conservative heuristic as before: cold =
max FFT share < 0.5% across the workloads (668 of 709 function
circuits), banded while aux stays within 1.6x, lookups within
max(2x, +4), and summed selectors <= 40; verify_claim excluded.

85 bands over 630 circuits: 730 -> 185 circuits, total committed width
33,331 -> 15,763 (-53%), at +13.6% (String.split) / +8.8%
(Array.extract_append) measured FFT cost. Kernel pins re-measured
(median +10.4%, shard +9.1%). Profiles, band data, and methodology in
cold-groups/.
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