Skip to content

Small value NeutronNova (with small-value witness generation)#125

Draft
wu-s-john wants to merge 59 commits into
microsoft:mainfrom
wu-s-john:small-value-compiler
Draft

Small value NeutronNova (with small-value witness generation)#125
wu-s-john wants to merge 59 commits into
microsoft:mainfrom
wu-s-john:small-value-compiler

Conversation

@wu-s-john

@wu-s-john wu-s-john commented May 10, 2026

Copy link
Copy Markdown
Contributor

Runs the most optimized version of small-value NeutronNova.

It works on top of PR #124, where it focuses on running the sumcheck accumulator on NeutronNova.

This PR takes it further an optimize the performance even more so that witness generation of SHA-256 are working with small numbers. It generates the witnesses as boolean and the matrix coefficient as i32. For computing the lagrange extended points of Aw, Bw and Cw, it still remains in i32. We do this to use small values as much as possible at the expense of slightly, increasing the number of constraints and witnesses. The results below demonstrate that the trade off is worthwhile.

The computations where executed on a 32 core AMD Ryzen 9 CPU. No GPU computation was done

neutronnova_prep_threads neutronnova_prove_threads neutronnova_prep_plus_prove_threads

You can run the experiments via:

 mkdir -p /tmp/nn_stage
  RUSTFLAGS="-C target-cpu=native" \
  NEUTRONNOVA_STAGE_TRACE=1 \
  TRACE_L0='0,s,3,4' \
  TRACE_THREADS='1,2,4,8,12,16,20,24' \
  TRACE_OUTPUT=/tmp/nn_stage/raw.csv \
  cargo bench --bench sha256_neutronnova

Add support for accumulating field × small_int products (i32, i64, i128)
with delayed modular reduction using generic Barrett reduction:

- SmallValueField<V> trait for small integer ↔ field conversion
- WideMul trait for widening multiplication
- BarrettReductionConstants with compile-time computed μ = ⌊2^512/p⌋
- SignedWideLimbs<N> accumulator for signed product sums
- DelayedReduction<i32/i64/i128> implementations for all fields
This replaces expensive field multiplications with native integer
arithmetic during the first ℓ₀ rounds when polynomial values are
guaranteed small.

Key components:

Lagrange accumulator infrastructure (src/lagrange_accumulator/):
- LagrangeAccumulators: precomputed A_i(v, u) values for all rounds
- LagrangeIndex/LagrangePoint/LagrangeHatPoint: type-safe domain indices
- LagrangeBasisFactory: barycentric Lagrange basis with O(D) evaluation
- extend_to_lagrange_domain: batch extension from {0,1}^ℓ₀ to U_D^ℓ₀
- EqRoundFactor: tracks α = eq(τ_{<i}, r_{<i}) across rounds
- Csr: compressed sparse row storage (2 allocations vs N+1 for Vec<Vec>)

Performance optimizations:
- Thread-local SpartanThreadState eliminates per-iteration allocations
- Delayed modular reduction via SignedWideLimbs accumulators
- Skip binary betas (Az·Bz = Cz on {0,1}^n for satisfying witnesses)
- Batched eq-weighted binding in transition phase

API:
- SmallValue trait: WideMul + Copy + Zero + Add + Sub + Send + Sync
- SmallValueEngine<SV>: blanket impl consolidates field requirements
- prove_cubic_small_value<E, SV, const LB>: main entry point

The prove_cubic_small_value function produces identical proofs to the
standard prove_cubic_with_three_inputs, verified via equivalence tests.
wu-s-john added 19 commits May 7, 2026 23:40
  Splits the small-value accumulator prep/prove path out of neutronnova_zk
  into a new small_neutronnova_zk module. Along with the move:

  - Consolidates build_accumulators_neutronnova: the full-small (l0 == ell_b)
    and partial-small (l0 < ell_b) variants now live in one function;
    _partial is removed from the lagrange_accumulator surface.
  - Factors the bit-reversed prefix gather + Lagrange-extend pattern into
    bit_rev_prefix_table and gather_and_extend_prefix helpers in
    lagrange_accumulator::extension, shared by the merged builder,
    build_extended_prefix_mle_evals, and the test that compares them.
  - Simplifies the small-value casting API: removes
    vec_to_small_for_extension_with_context; the call sites in
    build_small_abc wrap with .map_err to prepend "accumulator prep
    {matrix} layer={idx}" to the context.
  - Drops the unused SmallSparseMatrix path from r1cs::sparse.
Generalize WideMul and raw SparseMatrix matvec over coefficient, witness, and output types.

Update small NeutronNova, small-value accumulation, provider fields, SHA small tests, and benchmarks to use the generic arithmetic path.
@wu-s-john wu-s-john changed the title Small value compiler Small value NeutronNova e2e May 10, 2026
@wu-s-john wu-s-john changed the title Small value NeutronNova e2e Small value NeutronNova e2e (with small-value witness generation) May 10, 2026
@wu-s-john wu-s-john changed the title Small value NeutronNova e2e (with small-value witness generation) Small value NeutronNova (with small-value witness generation) May 10, 2026
@wu-s-john

wu-s-john commented May 11, 2026

Copy link
Copy Markdown
Contributor Author

Here are results on a Macbook Pro M1:

bytes steps mode l0 threads witness gen ms commitment ms NIFS ms outer sumcheck ms MLE eval ms inner sumcheck ms PCS opening ms prove ms prep+prove ms
512 8 full-field 0 10 13 6 19 5 1 5 14 57 79
512 8 small-accum/l0-3 3 10 1 3 4 8 1 5 15 48 57
512 8 small-value 0 10 13 7 15 7 1 6 14 57 86
1024 16 full-field 0 10 21 12 28 6 1 5 13 67 104
1024 16 small-accum/l0-3 3 10 1 5 9 7 1 5 13 49 63
1024 16 small-accum/l0-4 4 10 1 5 8 7 2 5 13 50 63
1024 16 small-value 0 10 20 12 22 5 1 4 13 59 107
2048 32 full-field 0 10 38 28 38 6 1 5 22 90 163
2048 32 small-accum/l0-3 3 10 2 13 31 8 2 6 14 76 106
2048 32 small-accum/l0-4 4 10 1 10 15 9 1 5 15 62 90
2048 32 small-value 0 10 32 24 31 8 1 4 14 74 158

wu-s-john and others added 7 commits June 12, 2026 10:23
…cuits

Four fixes, each with a regression test:

- SplitMultiRoundR1CSInstance::validate and SplitR1CSInstance::validate now
  re-check the vector lengths enforced by new() before any indexing, since
  deserialized proofs bypass new(). Malformed proofs (wrong-length
  comm_w_per_round, challenges_per_round, or public_values) previously
  panicked the verifier; they now return ProofVerifyError.

- NeutronNova NIFS witness fold takes the truncated fast path only when the
  step shape has no rest witness (num_rest_unpadded == 0). Step circuits with
  both precommitted and rest variables previously had their rest segment
  zeroed out of the folded witness and partial commitment fold, producing
  proofs that fail verification. Applied to both the field path and the
  small-accumulator fold helper.

- Re-synthesis before each prove now preserves public inputs allocated
  during the shared/precommitted phases (PrecommittedState::num_prep_inputs,
  serde-default 0 for old prep states) instead of truncating them away, so
  circuits may inputize public IO in any phase. The publics extraction in
  spartan_zk and bellpepper now returns a clear SynthesisError instead of
  panicking when a circuit inputizes fewer values than the shape expects.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… unified hot loops

R1 — single post-NIFS pipeline. finish_zk_proof() now carries the ~350-line
tail (batched outer/inner sum-checks, final rounds, NovaNIFS + relaxed
Spartan, c_eval folding, PCS prove, proof assembly) for BOTH the field path
(NeutronNovaZkSNARK::prove) and the small-accumulator path (prove_small),
which previously duplicated it verbatim. Also unifies setup/setup_small via
setup_from_shapes() and the NIFS prover head via prepare_nifs_inputs().
prove_small now also strips per-instance shared commitments (smaller proofs;
verify already tolerated both forms).

R2 — SpartanRoundSchedule / NeutronRoundSchedule (zk.rs) are the single
source of truth for multi-round circuit indices and challenge/public-value
counts; prover and verifier both derive indices from them instead of
hand-computed arithmetic mirrored across three files.

R3 — VcDriver (bellpepper/r1cs.rs) bundles the verifier circuit, multi-round
state, shape, and commitment key that previously threaded as a quintuple
through every sum-check and process_round call site; commit_round() is now
the single place enforcing the commit-then-squeeze discipline.

R4 — hot-loop dedupe: prove_helper and the AB-only fold-extension variant
collapse into one tensor_eq_fold_terms worker with const generics
(COMPUTE_E0, WITH_CZ); one set of fold primitives (fold_in_place,
fold_quad_chunk, fold_layer_pair_into, fold_final_layer_pairs) replaces
eight inline copies of the lo/hi fold pattern across both NIFS files.
Benchmarked prove/2048B/t8 across full-field, small-value, and small-accum
l0-3/4/5: all deltas within criterion noise, accumulator still beats
baseline at every cell.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- StepMatVecCache owns the deterministic step matvec/i64-mirror/
  large-positions triple and establishes its zeroed-at-large-positions
  invariant in one constructor; NeutronNovaPrepZkSNARK collapses three
  loosely-coupled fields into one Option<StepMatVecCache>, and
  NeutronNovaNIFS::prove takes the cache as a single argument.
- digest::bincode_write replaces the repeated bincode-config boilerplate in
  both verifier-key Digestible impls (identical bytes, digests unchanged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…chinery

No backend ever consumed annotations or namespaces (small shapes are purely
structural), so the trait shrinks from 7 methods + Root/SmallNamespace
machinery to alloc/alloc_input/enforce + is_witness_generator. A blanket
impl for &mut CS keeps by-value-generic gadget helpers callable, mirroring
bellpepper's pattern.

- All gadget and circuit call sites drop annotation arguments and
  namespace() wrappers; label-only parameters (block_idx/prefix/loop
  indices) are removed.
- small_sha256_int_with_prefix and
  small_sha256_compression_function_int_with_prefix are folded into their
  non-prefix variants: prefixes only fed namespace labels, which no longer
  exist.
- The bellpepper bridge synthesizes unique lazy names (sv{n}/sc{n}) since
  TestConstraintSystem requires them; production backends never evaluate
  the closures.
- NoBatchEq loses its label counters and its now-empty namespace plumbing.

Shapes are unchanged (names never influenced them) — verified by the
field-vs-small-compiler equivalence tests and the full suite (299 passing).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The five build_accumulators_* functions shared ~70% of their bodies. Three
shared pieces now exist once:

- neutron_eq_setup: tensor-split eq caches, suffix fold weights, and the
  β→prefix-index tables (NeutronEqSetup) consumed identically by every
  NeutronNova input-format variant
- collect_and_scatter_betas: the reduce-nonzero-βs + scatter-into-rounds
  tail shared by all five builders (generic over the unreduced product type,
  so both SpartanThreadState and the ext-workspace ProductState use it)
- run_outer_fold: the serial-vs-rayon fold/reduce driver

The per-format gather kernels (the hot inner loops) are untouched.
build_accumulators_spartan now also gets the small-workload serial path.

Verified with alternated A/B benches on prove/small-accum/l0-4 (pre avg
71.3 ms vs post avg 70.4 ms; prep+prove 98.9 vs 99.5 ms) — neutral within
machine noise. Full suite passes (299), including the preextended-vs-generic
and NIFS polynomial-equivalence cross-checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SmallAbc, PrefixWorkspace, ExtendedPrefixMleEvals, and the prefix/suffix
fold helpers that operate on them move to a child module
(small_neutronnova_workspace.rs), shrinking small_neutronnova_zk.rs to the
prep/prove pipeline and the accumulator NIFS driver. Pure code motion; the
no-custom-macros introspection test now covers both files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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