Skip to content

feat(akita)!: externalize trusted schedules and guide recursive offloading - #1844

Merged
moodlezoup merged 27 commits into
mainfrom
codex/akita-integration-c02ed7928
Sep 5, 2026
Merged

feat(akita)!: externalize trusted schedules and guide recursive offloading#1844
moodlezoup merged 27 commits into
mainfrom
codex/akita-integration-c02ed7928

Conversation

@quangvdao

@quangvdao quangvdao commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

With #1842 merged, this restacks Jolt's Akita integration on current main and updates it to the quotient-free, external-schedule Akita stack.

  • replace compiled Rust schedule tables with canonical runtime .aks catalogs;
  • make verifier preprocessing own the exact trusted catalog bytes and restore the same immutable catalog instance after serialization;
  • route setup, commit, prove, decode-budget validation, and verify through that setup-owned catalog;
  • bind the validated catalog digest into the Fiat-Shamir setup identity;
  • keep fix(akita)!: eliminate redundant opening framing and rehashing #1842's lean proof wire: a fixed 32-byte schedule selection followed by the headerless backend proof body;
  • use direct trace schedules below logical log_T = 21, then setup-offloaded schedules from log_T = 21 upward;
  • adapt advice and committed-program profiles with Akita's guided planner while freezing the scalar trace schedule's geometry and opening parameters.

No planner policy, catalog bytes, catalog path, or proof-shape description is carried in the proof.

Stack and exact revisions

The ancestry-only merge 8cb334b19 makes the merged #1842 commit on main a parent without changing the PR tree: both 8cb334b19 and the previous green head 48643c013 have tree cf874a6f1e187d2d3fa551557a0d33b4719b0f40. The PR base is now main, so the merged #1841/#1842 work is excluded from this PR's review diff.

Design

Trusted schedule ownership

The host loads and validates the base K16, K256, and dense catalogs explicitly. Preprocessing admits the exact reachable advice and committed-program profiles, canonicalizes the resulting catalog, and serializes those bytes in AkitaVerifierSetup. Deserialization validates the artifact before recreating the scheme cache.

The prover and verifier therefore derive their scheme instances from the same trusted artifact. There is no process-global schedule registry and no verifier-time replanning or filesystem discovery.

Guided adaptation

For grouped roots, the selected scalar trace row remains authoritative. Guided planning freezes its:

  • root and fold geometry;
  • direct versus setup-offloaded topology;
  • opening and ring-relation parameters;
  • scalar trace schedule selection.

Only the new advice/bytecode/program-image precommit profiles and suffix quantities affected by them are replanned. Missing or unsupported reachable rows fail preprocessing rather than failing later during proving.

This intentionally optimizes fast adaptation to relatively small precommitted objects; it does not claim the globally optimal schedule that a full fresh solve might find.

Decode and transcript boundary

The verifier resolves the 32-byte selection against the setup-owned catalog before deriving allocation budgets or decoding the backend proof. The shape guard and final batched verification use the same scheme instance. The validated catalog digest is absorbed into the verifier setup transcript.

Recursive cutoff: isolated four-thread measurements

Fresh measurements ran on temporary ASCII Box bx_8ge5dtvp: large 8-vCPU/16-GB AMD EPYC-Rome VM, Ubuntu x86_64, no injected environment or secrets. The release process was pinned to CPUs 0-3, with four prover threads and an explicitly single-threaded verifier. Each size has seven retained alternating direct/offloaded pairs after one warm-up pair. Prover time is commitment plus proof generation.

Values below are medians; bracketed values are percentile-bootstrap 95% intervals for the paired median.

logical trace direct prover offloaded prover paired overhead direct verify offloaded verify paired speedup
2^20 10.252 s 10.669 s +4.62% [3.67, 5.69] 33.482 ms 20.858 ms 1.61x [1.53, 2.11]
2^21 19.563 s 20.223 s +3.25% [3.01, 3.62] 43.141 ms 26.968 ms 1.57x [1.39, 1.89]
2^22 37.743 s 38.559 s +1.62% [0.67, 2.80] 49.796 ms 27.400 ms 1.83x [1.74, 2.10]
2^23 75.985 s 77.014 s +1.38% [0.77, 1.49] 74.665 ms 34.399 ms 2.21x [1.96, 2.48]
2^24 149.329 s 150.417 s +0.48% [-0.22, 0.90] 91.196 ms 33.935 ms 2.65x [2.55, 2.69]

This isolated host does not support the current log_T = 21 crossover. 2^23 is the first size whose paired median clears the approximate 2x target, with only 1.38% median prover overhead, although its bootstrap interval still touches just below 2x. 2^24 is the first conservative crossover: all seven pairs exceed 2x and the interval lower bound is 2.55x.

The checked-in RECURSIVE_TRACE_LOG_T_CUTOVER = 21 remains the implementation in this head; the benchmark evidence recommends moving it to 23 for the intended approximate-2x policy, or 24 for a strict lower-confidence-bound policy, followed by catalog regeneration.

Raw run: 70 retained samples, 2 warm-ups, zero failures/retries; JSONL SHA-256 134a47af8f3e5b6a95a55ed18c0f2f52feec50f277a86e04cc8477c2d7e97911; benchmark binary SHA-256 ee28ecc809b975fa2e6ed0401ff3a3ec7c3837d55c044a966afabc8b829b7d68.

Complete Akita batched PCS proof-size cost

logical trace direct proof offloaded proof added bytes overhead
2^20 68,913 B 71,203 B +2,290 B +3.32%
2^21 68,889 B 71,749 B +2,860 B +4.15%
2^22 69,004 B 72,052 B +3,048 B +4.42%
2^23 69,138 B 72,041 B +2,903 B +4.20%
2^24 69,709 B 73,621 B +3,912 B +5.61%

These are complete serialized Akita batched PCS proofs (the fixed schedule selection plus backend body), not complete outer Jolt proofs.

Number of setup-offloaded levels

The production trace packs to one physical polynomial. The current checked-in catalogs contain:

logical trace range selected family setup-offloaded recursive folds
below 2^21 K16/direct 0
2^21 .. 2^24 K16 1
2^25 K256 1
2^26 .. 2^30 K256 2

The catalogs extend through logical log_T = 30. Two-polynomial adapter/tamper rows are also retained: their only topology difference is that the K256 log_T = 25 compatibility row already offloads two folds. Moving the cutoff to 23 would make the 2^21 and 2^22 production rows direct while leaving the offload depth at 2^23 and above unchanged.

Compatibility

This is a protocol-epoch-breaking change. Old generated Rust catalogs, old schedule selections, serialized Akita-mode proofs, and preprocessing produced against the previous Akita revision are intentionally incompatible. There is no fallback/default catalog.

Change map

Area Files Purpose
Catalog artifacts and generation crates/jolt-akita/schedules/, src/schedules/, gen_jolt_schedules.rs Check in canonical external artifacts and remove generated Rust tables
Setup ownership scheme.rs, adapters.rs, schedule_registry.rs Validate, extend, serialize, restore, and bind exact catalogs
Proof boundary shape_guard.rs, native_batching.rs Resolve trusted rows before proof allocation/decoding and verify with the same scheme
Advice/program integration jolt-openings, jolt-prover, jolt-prover-legacy Thread explicit catalog context through object commitment and grouped setup
Regression coverage Jolt-Akita tests, verifier fixtures, FS inventory, CI Drift, replay, restoration, malformed-catalog, byte-wire, and transcript checks

Validation

Completed locally at 8cb334b1987a29e19e013d4c31a44f93dbdda5ca after restacking on main:

  • merge tree cf874a6f1e187d2d3fa551557a0d33b4719b0f40 is byte-identical to prior head 48643c013;
  • python3 scripts/check_style_invariants.py --base origin/main: clean;
  • cargo fmt --all --check, taplo fmt --check, and git diff --check origin/main...HEAD: clean;
  • cargo nextest run -p jolt-akita: 47 passed;
  • cargo nextest run -p jolt-verifier --test fs_obligations --features fs-audit --cargo-quiet: 1 passed.

The push triggered fresh CI for the ancestry-only merge. The immediately preceding content-identical head 48643c013 had 42/42 reported checks successful, including the complete Jolt build/test workflow, Akita fixtures, schedule drift, byte parity, FS attacks/obligations, formatting, style, Clippy, and formal-verification jobs.

Earlier full local validation on the same implementation also passed:

  • focused grouped/adaptation schedule tests: 6 passed, 2 skipped;
  • modular prover Akita coverage: 18 passed, 2 skipped;
  • legacy prover Akita coverage: 542 passed, 1 skipped;
  • verifier Akita coverage: 85 passed, 9 skipped;
  • canonical catalog regeneration and byte comparison;
  • cargo clippy -p jolt-akita --all-targets -- -D warnings;
  • cargo clippy -p jolt-prover --all-targets -- -D warnings;
  • typos.

Review order

  1. crates/jolt-akita/src/scheme.rs and schedule_registry.rs: catalog authority and guided admission.
  2. shape_guard.rs and native_batching.rs: trusted selection before decode and same-instance verification.
  3. jolt-openings plus prover call sites: explicit setup context.
  4. external artifacts/generator and drift tests.
  5. FS inventory, fixtures, and CI.

Stack the current Akita profiling and lean-wire papercuts on Jolt main, pin the quotient-free Akita tip, and replace compiled schedule tables with canonical runtime artifacts. Build exact grouped catalogs per setup, serialize them with verifier preprocessing, bind their digest into the transcript, and resolve proof shapes through the same trusted scheme instance.
Record the validated catalog-digest absorption in the Fiat-Shamir inventory and keep external artifact file I/O outside the profiling setup measurement while retaining catalog admission and key construction inside it.
…ation-c02ed7928

# Conflicts:
#	crates/jolt-akita/src/native_batching.rs
#	crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

📏 PR diff

Note

Actual code changed: 3,537 lines

🟢 +860 added    🔴 −2,677 removed

Tests, docs, fixtures, and helper scripts excluded.

Category 🟢 Added 🔴 Removed
⌨️ Actual code +860 −2,677
🧪 Tests +621 −642
📚 Docs +165 −119
📦 Fixtures +25 −21
🔧 Helper scripts +2,578 −4
Total diff +4,249 −3,463

Every changed line is classified once.

@github-actions github-actions Bot added the no-spec PR has no spec file label Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Warning

This PR has more than 500 changed lines of actual code (3488 added+removed; tests, docs, fixtures, and helper scripts are excluded) and does not include a spec.

Large features and architectural changes benefit from a spec-driven workflow.
See CONTRIBUTING.md for details on how to create a spec.

If this PR is a bug fix, refactor, or doesn't warrant a spec, feel free to ignore this message.

# Conflicts:
#	crates/jolt-akita/src/adapters.rs
#	crates/jolt-akita/src/native_batching.rs
#	crates/jolt-akita/src/shape_guard.rs
#	crates/jolt-verifier/tests/fs_inventory/absorb-sites.inventory
@quangvdao
quangvdao changed the base branch from codex/akita-lean-opening-wire to main September 4, 2026 18:36
Five identical `fn schedule_artifacts` helpers (jolt-akita's scheme tests,
legacy's packed tests, the jolt-verifier akita fixtures, and both modular
prover akita suites) plus eight open-coded copies of the same expression
all wrapped `from_default_directory()` in an `Arc` and aborted on failure.
Replace them with `AkitaScheduleArtifacts::shared_from_default_directory()`,
the shared-handle form every setup call already wanted.

An inherent associated fn rather than a `test-utils` module: jolt-verifier
has no jolt-akita dependency and reaches the type only through legacy's
`pub use`, and a re-exported type carries its inherent methods where a free
function would not. jolt-akita's own tests and benches would also have
needed a self dev-dependency. This needs no manifest change anywhere, and
`profile.rs` is a real production caller rather than test-only scaffolding.

Loader call counts per file are unchanged, so the `Arc::ptr_eq` provenance
guards on advice setup (packed.rs) still compare what they did before; the
doc records that constraint.

Extract `AkitaScheduleArtifacts::packaged_directory()` for the second rule,
`<jolt-akita>/schedules`, previously spelled out in the `from_default_directory`
fallback, the shape-guard test, and the catalog regeneration guards. Those
two guards deliberately keep reading the packaged directory rather than the
default loader, since they validate the committed artifacts and must not be
redirected by `JOLT_AKITA_SCHEDULE_DIR`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Benchmark comparison (crates)

Same-runner A/B: base_run = 628713f (merge target) vs pr_run = df3fdbc (PR head merged onto it). Bold = faster run; Δ colored when the difference exceeds the ±10% noise threshold.

No differences above the ±10% noise threshold.

All 62 benchmarks
Benchmark base_run (merge target) pr_run (this PR) Δ
EqPolynomial::evals/17 1.4±0.05ms 1.3±0.05ms -3.3%
EqPolynomial::evals/19 5.3±0.25ms 4.9±0.04ms -7.5%
EqPolynomial::evals/20 11.0±0.56ms 10.0±0.24ms -9.1%
EqPolynomial::evals/22 41.0±1.30ms 39.1±1.25ms -4.8%
EqPolynomial::evaluations/14 385.5±13.64µs 374.0±9.09µs -3.0%
EqPolynomial::evaluations/18 4.6±0.17ms 4.4±0.06ms -4.3%
EqPolynomial::evaluations/20 19.8±1.08ms 18.3±0.55ms -7.4%
Polynomial::bind/14 96.6±5.00µs 93.2±3.68µs -3.5%
Polynomial::bind/18 1.4±0.06ms 1.3±0.02ms -4.9%
Polynomial::bind/20 5.3±0.12ms 5.1±0.06ms -4.1%
Polynomial::evaluate/20 29.4±1.49ms 26.9±0.65ms -8.7%
append_bytes/Blake2b/256B 255.4±4.47ns 248.5±3.70ns -2.7%
append_bytes/Blake2b/32B 40.7±3.48ns 37.9±1.62ns -6.9%
append_bytes/Keccak/256B 533.9±3.23ns 545.6±61.05ns +2.2%
append_bytes/Keccak/32B 28.7±2.13ns 27.0±1.38ns -5.7%
append_bytes/Poseidon/256B 210.7±6.46µs 203.9±2.83µs -3.2%
append_bytes/Poseidon/32B 68.4±2.79µs 63.9±1.09µs -6.5%
challenge/Blake2b 494.5±23.85ns 483.7±27.73ns -2.2%
challenge/Keccak 263.9±9.93ns 254.8±7.83ns -3.5%
challenge/Poseidon 32.6±1.33µs 31.9±1.45µs -2.1%
field arithmetic/add/fp128_a7f7 3.6±0.04µs 3.7±0.19µs +2.5%
field arithmetic/add/fp32_c99 1.7±0.03µs 1.7±0.07µs -0.3%
field arithmetic/add/fp64_c59 2.2±0.19µs 2.1±0.24µs -4.8%
field arithmetic/mul/fp128_a7f7 9.4±0.03µs 9.4±0.37µs -0.8%
field arithmetic/mul/fp32_c99 3.4±0.08µs 3.4±0.14µs +0.2%
field arithmetic/mul/fp64_c59 4.5±0.07µs 4.4±0.13µs -1.9%
field arithmetic/square/fp128_a7f7 10.9±0.14µs 11.0±0.71µs +1.5%
field arithmetic/square/fp32_c99 4.3±0.18µs 4.3±0.15µs -0.4%
field arithmetic/square/fp64_c59 4.0±0.06µs 3.9±0.18µs -2.5%
field arithmetic/sub/fp128_a7f7 3.0±0.07µs 2.8±0.03µs -5.9%
field arithmetic/sub/fp32_c99 1.2±0.03µs 1.2±0.01µs -1.3%
field arithmetic/sub/fp64_c59 1.5±0.05µs 1.5±0.06µs +1.0%
fp64_prime63_offset259/ext2_packed_mul 16.5±0.46ns 16.1±0.10ns -2.7%
fp64_prime63_offset259/ext2_scalar_mul 5.3±0.06ns 5.2±0.07ns -1.1%
fp64_prime63_offset259/packed_mul 6.0±0.05ns 5.9±0.13ns -0.7%
fp64_prime63_offset259/scalar_mul 1.2±0.02ns 1.2±0.02ns -0.8%
g1_add 276.8±35.38ns 267.8±10.54ns -3.3%
g1_deserialize_bincode 5.7±0.12µs 5.8±0.29µs +0.9%
g1_double 127.0±4.37ns 130.6±2.55ns +2.9%
g1_msm/1024 8.7±0.34ms 8.8±0.38ms +0.6%
g1_msm/16 363.9±16.52µs 387.9±20.05µs +6.6%
g1_msm/256 2.9±0.12ms 3.0±0.14ms +4.8%
g1_msm/4 166.1±9.08µs 173.0±8.96µs +4.2%
g1_msm/64 1.0±0.03ms 1.1±0.03ms +4.4%
g1_scalar_mul 40.4±0.93µs 41.1±1.15µs +1.6%
g1_serialize_bincode 77.2±2.58ns 76.7±4.95ns -0.6%
g2_msm/16 1.4±0.04ms 1.4±0.04ms +1.2%
g2_msm/256 10.9±0.52ms 11.1±0.56ms +1.4%
g2_msm/4 576.9±29.64µs 552.1±17.44µs -4.3%
g2_msm/64 3.9±0.13ms 4.0±0.07ms +1.2%
g2_scalar_mul 214.3±3.91µs 218.5±3.26µs +1.9%
gt_scalar_mul 655.2±13.63µs 658.4±26.51µs +0.5%
multi_pairing/16 4.2±0.26ms 4.3±0.26ms +1.7%
multi_pairing/2 918.6±26.23µs 930.2±33.51µs +1.3%
multi_pairing/4 1.4±0.03ms 1.4±0.05ms +0.5%
multi_pairing/8 2.4±0.16ms 2.3±0.11ms -1.7%
pairing 688.6±19.56µs 690.4±15.78µs +0.3%
pedersen_commit/1024 8.5±0.13ms 8.7±0.35ms +1.7%
pedersen_commit/16 427.0±27.18µs 423.3±12.48µs -0.9%
pedersen_commit/256 2.9±0.03ms 2.9±0.02ms -0.3%
pedersen_commit/4 211.1±7.38µs 211.2±5.06µs +0.0%
pedersen_commit/64 1.1±0.02ms 1.1±0.03ms -0.1%

moodlezoup and others added 3 commits September 4, 2026 23:18
The family filter compared `ends_with(selector)` against names like
`jolt-fp128-dense-bounded`, so the documented `dense` argument matched
nothing and the generator silently wrote no artifacts. Match the
selector as an infix and fail loudly when it selects no family.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`invalid_setup` was declared as a `&dyn Display` closure in three
functions, and that signature forced a wrapping closure at every one of
its fifteen uses. Make it a free function beside `invalid_batch`,
`akita_error`, and `commit_failed`, taking `impl ToString` like they do,
so call sites pass the fn item directly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`emit::regen` was `pub(crate)` and `MAX_PROVISIONED_ROWS` was `pub`
only for tests the registry removal deleted; every remaining use is
inside the defining module.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
moodlezoup and others added 2 commits September 4, 2026 23:19
Both catalog guards repeated the reason they read `packaged_directory()`
instead of the default loader; that reason is the accessor's own doc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`from_default_directory` and `DIRECTORY_ENV` were `pub` with exactly one
caller each, `shared_from_default_directory`. Deployments that must
handle a missing catalog use `from_directory` with a path they own, so
the fallible env-aware form has no external contract to keep.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@moodlezoup
moodlezoup merged commit 7de83dd into main Sep 5, 2026
52 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no-spec PR has no spec file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants