Repository navigation
Reduce recursive verifier rows and update the Jolt pin - #277
Open
Acentelles wants to merge 15 commits into
Open
Acentelles wants to merge 15 commits into
Acentelles wants to merge 15 commits into
Conversation
This was referenced Oct 27, 2026
Moves every a16z/jolt dependency (jolt-field and jolt-poly in the workspace and fuzz/, plus jolt-sdk and jolt-sdk-macros in profile/akita-recursion) from 1618f4b6d to Jolt main e1d8e02b5, whose jolt-field has the kernels the following verifier changes use (Field::signed_sum, Field::sum_of_products, Field::dot_product, ExtField::weighted_dot_base_rows, MulBaseUnreduced::dot_base). In profile/akita-recursion, Jolt main's jolt-sdk pulls in jolt-akita, which pins Akita by git revision; a patch table builds it against this checkout so the profile graph keeps one Akita and one jolt-field. Jolt main's provable verify function returns Result, so the host reports the verifier error instead of negating a bool. Manifests and that host call only; the lockfiles are regenerated in a following commit.
…base Adds generated static base tables for the D64 selective-L2 operator-norm sampler, with the generator script. The tables are compiled in only with the ont-static-base feature, which is off by default; without it the sampler is unchanged.
The compression relation table records one event per (gadget bit, source row, coefficient block) for every recomposition and one event per column for every compression row, and the verifier evaluated the table's multilinear extension event by event. Each of these families is an arithmetic progression of aligned events whose scalars are a row factor times a shared table: the lifted gadget powers, or one evaluated compression matrix's columns. The builder now records each such family as one group, and `CompressionRelationWeights::evaluate_at_point` evaluates a group's extension by factor: the shared alpha low factor, times dyadic blocks over the high index (a geometric table closes each block in a product of log factors, any other table caches the block's extension), or a loop over the per-event equality window when the stride is not a power of two. Every split is over disjoint digit positions of one index, so the value is the multilinear extension of the same dense table at every field point. A family that fails any precondition (alignment, domain, overflow, a zero gadget entry, a zero source ring dimension) is pushed event by event, so accepted inputs and errors are unchanged. Prover-side materialization (`into_sparse_entries`, dense accumulation) expands the groups back into their events, so prover output is unchanged. No proof byte, transcript or verifier decision changes. Unit tests compare the grouped path with the per-event path and with dense multilinear evaluation at random, zero, one and mixed points, including every evaluation tier, unaligned runs, clipped runs, interior and trailing zero columns and the error paths. The reduced-program test now counts and domain-checks grouped events as well.
…event evaluation Add the diagnostic feature `compression-weights-crosscheck` to `akita-types` (forwarded by `akita-pcs`). No default feature enables it, and without it the compiled code is unchanged: the only edits to existing code are a `cfg` attribute on `evaluate_at_point` and on the recomposition dispatcher, and a `cfg`-gated observation call at the start of `push_column_group`; every other existing line keeps its text and position. With the feature, `CompressionRelationWeights::evaluate_at_point` returns the same value and also: - evaluates the same table event by event (every group expanded into its events) at the requested point and at four seeded random points, and compares each value with the grouped evaluation; - counts the table's groups per evaluation tier (geometric dyadic blocks, cached dyadic blocks, per-event equality window) and its ungrouped events. Each recomposition call and compression row of the builders records whether the family was grouped or pushed event by event, with the reason for a fallback, and compares the grouped events with those of the canonical per-event loop on the same arguments. A difference is counted and returned as `AkitaError::Internal`, so verification fails closed. Counters are available from `crosscheck::report()`, and each evaluation emits one `tracing` event at INFO (target `akita_types::compression_weights_crosscheck`) whose message is a JSON object with that evaluation's tiers and its table's builder outcomes. Tests: with the feature, a table whose geometric closed form disagrees with its events is rejected; builder outcomes, fallback reasons and every tier are recorded; and `akita_fp128_e2e` runs dense, one-hot and precommitted fp128 roundtrips and checks that the cross-check ran with no difference. Every other roundtrip in that file is cross-checked too when the feature is on.
The feature-gated event comparison returned Result<Vec<((usize, usize, usize), E)>, AkitaError>, which clippy::type_complexity rejects with compression-weights-crosscheck on. Name it EventSums<E>. Feature-gated lines only; the default build is unchanged.
Port onto akita main (LayerZero-Labs#188 ResidualTerms::{Table, Streamed}): the separated residual is prepared per view in DenseLeftOverlap::materialize (the plan no longer sees the equality window) and only from a tabulated residual; a streamed residual declines to the existing walk. The family scalar, which main now applies inside evaluate_residual, is applied to the separated value in the same way.
Port onto akita main (LayerZero-Labs#188 ResidualTerms::{Table, Streamed}): the run accumulator is called from DenseLeftOverlap::materialize on the same replaced comment line; it reads only a tabulated residual (a streamed residual declines) and, under tensor-no-carry, takes the per-view separated residuals prepared in materialize instead of a view field. The tests build tabulated views and no longer apply the family scalar on top of evaluate_residual, which includes it on main.
Port onto akita main (LayerZero-Labs#188): keeps the earlier port (tabulated residuals only, scalar on main inside evaluate_residual). The run accumulator now takes a plain bool (some view took the no-carry separated path) instead of a cfg-gated slice argument, because rustfmt drops a cfg-gated call argument; the call site in DenseLeftOverlap::materialize is written in rustfmt form and the original comment line is restored (not line-preserving on this base).
…-scan sites Feature setup-sop-structured (default off; forwarded by akita-pcs) sums the cached structured E, T and Z contractions of the verifier setup scan through Field::dot_product and Field::sum_of_products. The scan-path kernels (the identity-path D/B/A weight loop and the B role terms) keep their current code. Field values are unchanged. Line-preserving: each structured call site selects through lb2_alt!, opened on the first and closed on the last line of the original statement (rustfmt::skip on the same line); every new item is appended after the last original line of its file, and the new module lb2.rs is declared at the end of mod.rs. Without the feature the expansion is the original tokens on their original lines.
Cargo.lock, fuzz/Cargo.lock and profile/akita-recursion/Cargo.lock resolved by cargo against the new a16z/jolt revision.
Acentelles
force-pushed
the
perf/verifier-recursion-levers
branch
from
October 30, 2026 02:02
11dde9a to
4f781ad
Compare
Update the manifest revisions and the three lockfile source IDs from e1d8e02b5 to 4e55c9a5. Dependency manifests and Jolt Cargo.lock are unchanged across this move; resolution and compilation are verified on the Linux gate host.
This branch has not been deployed
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.
This reduces Akita verifier work inside a zkVM guest and pins Jolt to$2^{12}$ and 21.171% at $2^{20}$ , with unchanged Akita proof bytes. The pin and unconditional changes account for most of the combined reduction; the optional-feature saving is reported separately below.
4e55c9a5f7033d1b88254df051e0bd8c4a4b0ec3. Againstmain(038db2d1), total verifier rows fall 18.794% at innerChanges
The pin commit updates all Jolt dependencies, patches the profile workspace to this Akita checkout to keep one field identity, and handles the profile host's
verifyreturningResult. A separate commit records the three regenerated lockfiles.The 11 retained changes, including their supporting tests:
Field::signed_sumfor coefficients of magnitude one or two (unconditional).ont-static-baseuses generated D64 operator-norm base tables, with the existing builder as fallback.compression-weights-crosscheckchecks grouped weights against expanded events and dense/roundtrip tests.weighted_dot_base_rows(unconditional).Ringfor their field constructors.tensor-no-carryseparates tabulated tensor residuals into high/low contractions.tensor-eq-runsaccumulates tensor equality weights over power-of-two runs.setup-sop-structuredbatches the structured E/T/Z contractions through dot products and sums of products.Two reworks make this independent of the reverted row kernels: factored weights call main's
OffsetEqWindow::eval, and reduced rows keep main'seval_ring_at_pows_fastin the nonshared path. Tensor paths decline streamed residuals and preserve main's family-scalar semantics. The cross-check roundtrips also pass the current witness-chunking argument. The redundantctutilsbump is omitted.Measurement
Jolt main plus the recursion example from #1911, with a host input-sizing patch, built against Akita main (BASE) or
6c25733560c72aca671a2a079991c8643310e807with the four optional features off/on. All builds use Joltfield-inline; Akita hashing runs in software. AMD Ryzen 9 9950X; one run per arm and size. Counts are total real plus virtual verifier rows, not timing claims.Derived from those measured totals:
There is no pin-only control: the pure pin benefit and the total contribution of all retained changes are not isolated. The outer Jolt measurement tree is held fixed and supplies its workspace
jolt-field. Complete serialized proof files are byte-identical across arms at each size; Akita proof-byte deltas are zero.Validation and limits
The latest commit moves the Jolt pin from
e1d8e02bto4e55c9a5(a16z/jolt main, which adds range checks on direct RAM loads); it changes only dependency pins and lockfiles. At the new head the x86 CI mirror below was re-run and every gate passed, and the rows above were re-measured. The test counts, the flipped-bit rejection and the clean rebuild reproduction were measured at the previous head.The x86 CI mirror passed format/TOML/typos, line/proof-stream/field-identity guards, all three CI Clippy configurations, dependency hygiene, docs, proof-channel roundtrips, six schedule-drift checks, both transcript-semantics suites, doc guardrails, portable x86/AVX2, profile smoke, and cargo-deny/audit. Nextest passed 2,051 tests (17 skipped); optional-feature nextest passed 2,061 (17 skipped). Feature-specific tests, three additional Clippy configurations, static-table equality/fallback tests, and compression cross-check unit/e2e tests passed.
The nightly fuzz check failed with debug-link relocation overflow on both main and the subject. AArch64 NEON, emulated AVX-512 IFMA, fuzz campaigns and the CI timing report were not run. Schedule regeneration was omitted because planner, schedules and artifacts are untouched. This is mirror evidence; GitHub CI must validate the pushed head.
All six honest proof traces accepted. A flipped opening bit was rejected by small ON with the same ELF. Size, feature-map, dependency-lock, proof-identity and byte gates passed; BASE/OFF/ON ELFs were distinct. The clean small ON rebuild reproduced rows and ELF exactly.
Risks and switches
The pin changes dependency resolution. Signed evaluation, factored weights and reduced-row batching affect every verifier; they have no feature switch and require reverting their commits to disable. Their test-only fixes have no production switch. Reverting the pin and locks also requires reverting code that uses the new field APIs. Generated tables must match the builder; grouped-weight and tensor fallback boundaries need continued coverage. Tensor edits are not line-preserving on main; structured sums preserve feature-off statement lines but add macro complexity. Row counts can vary with crate source identity and link layout.
All optional switches default off. Remove the following features from every enabling dependency edge to disable them:
akita-challenges/ont-static-baseakita-algebra/tensor-no-carryakita-algebra/tensor-eq-runsakita-verifier/setup-sop-structuredand itsakita-pcsforwardakita-types/compression-weights-crosscheckand itsakita-pcsforward (disables checking, not factored evaluation)