Skip to content

Add sigma-v4 commit-and-prove backend: per-proof cost no longer scales with adapter size#61

Closed
BidhanRoy wants to merge 6 commits into
mainfrom
claude/lora-1000x-perf-a0p7ar
Closed

Add sigma-v4 commit-and-prove backend: per-proof cost no longer scales with adapter size#61
BidhanRoy wants to merge 6 commits into
mainfrom
claude/lora-1000x-perf-a0p7ar

Conversation

@BidhanRoy

@BidhanRoy BidhanRoy commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Replace the per-invocation halo2 circuit (which re-hashed and re-range-checked
every adapter weight inside every proof) with a commit-and-prove protocol over
ristretto255:

  • Adapter setup, once per manifest: salted Pedersen row commitments to A and B,
    per-weight commitments, an aggregated Bulletproofs range proof pinning every
    weight to the exact [-value_bound, value_bound] interval, and a Schnorr link
    proof tying row and value commitments together. The pinned adapter
    commitment string is the SHA-256 of the deterministic commitment core.
  • Invocation proofs: Pedersen commitments to the rounding quotients and
    remainders of the exact three-stage quantized pipeline, Fiat-Shamir random
    projections (Schwartz-Zippel) of each matrix equation onto scalar equations
    proven by generalized Schnorr plus one rank-sized quadratic inner-product
    sigma protocol, and aggregated Bulletproofs for the exact remainder and
    quotient intervals. Per-proof work is O(in + rank + out) group operations.

Statement semantics are unchanged: same canonical half-up rounding, same exact
remainder intervals, same value/intermediate bounds, same transcript binding
and verifier trust boundary. Assumptions stay in the same class (discrete log
plus Fiat-Shamir; halo2-IPA was already DLOG-based) and adapter hiding
improves: commitments are perfectly hiding and salted where the v3 Poseidon
chain was unsalted. v3 halo2 artifacts (schema 2) still verify through the
legacy path, and the halo2 backend remains in the crate.

Measured on the 4-core benchmark host (same harness as the v3 results):

  • end-to-end 16x2x16 x8 invocations: prove 71.4s -> 0.84s, verify 3.4s -> 0.26s
  • end-to-end 32x4x32 x6: prove 107.4s -> 1.14s
  • single proof 768x2x256: prove 81s warm / 429s cold -> 1.0s (no keygen, no
    warm/cold split), verify 1.3s -> 0.14s; proving memory GBs -> MBs
  • 768x4x768 and 768x4x2304: previously out of memory on a 15GB host, now
    seconds per proof

…s with adapter size

Replace the per-invocation halo2 circuit (which re-hashed and re-range-checked
every adapter weight inside every proof) with a commit-and-prove protocol over
ristretto255:

- Adapter setup, once per manifest: salted Pedersen row commitments to A and B,
  per-weight commitments, an aggregated Bulletproofs range proof pinning every
  weight to the exact [-value_bound, value_bound] interval, and a Schnorr link
  proof tying row and value commitments together. The pinned adapter
  commitment string is the SHA-256 of the deterministic commitment core.
- Invocation proofs: Pedersen commitments to the rounding quotients and
  remainders of the exact three-stage quantized pipeline, Fiat-Shamir random
  projections (Schwartz-Zippel) of each matrix equation onto scalar equations
  proven by generalized Schnorr plus one rank-sized quadratic inner-product
  sigma protocol, and aggregated Bulletproofs for the exact remainder and
  quotient intervals. Per-proof work is O(in + rank + out) group operations.

Statement semantics are unchanged: same canonical half-up rounding, same exact
remainder intervals, same value/intermediate bounds, same transcript binding
and verifier trust boundary. Assumptions stay in the same class (discrete log
plus Fiat-Shamir; halo2-IPA was already DLOG-based) and adapter hiding
improves: commitments are perfectly hiding and salted where the v3 Poseidon
chain was unsalted. v3 halo2 artifacts (schema 2) still verify through the
legacy path, and the halo2 backend remains in the crate.

Measured on the 4-core benchmark host (same harness as the v3 results):
- end-to-end 16x2x16 x8 invocations: prove 71.4s -> 0.84s, verify 3.4s -> 0.26s
- end-to-end 32x4x32 x6: prove 107.4s -> 1.14s
- single proof 768x2x256: prove 81s warm / 429s cold -> 1.0s (no keygen, no
  warm/cold split), verify 1.3s -> 0.14s; proving memory GBs -> MBs
- 768x4x768 and 768x4x2304: previously out of memory on a 15GB host, now
  seconds per proof
Replace per-invocation Bulletproofs range proofs with a sumcheck-based LogUp
lookup argument as the default range engine. Every committed quotient and
remainder is decomposed into 8-bit digits inside one vector Pedersen
commitment; a LogUp identity over a 256-entry table is proven by two
sumchecks whose round polynomial coefficients are sent as Pedersen
commitments, never in the clear. All sumcheck verifier relations (round
consistency, claim chaining, final evaluations, MLE openings, digit
recomposition) are linear over committed scalars and fold into a single
generalized Schnorr proof; the one bilinear final-evaluation check uses a
product sigma protocol. Zero knowledge is structural (only hiding
commitments and uniform responses are sent) and soundness is standard
sumcheck + Schwartz-Zippel + Pedersen binding under the same discrete-log +
Fiat-Shamir assumptions. ZKLORA_RANGE_ENGINE=bulletproofs opts back into
compact proofs; the adapter setup keeps Bulletproofs (one-time, size-
sensitive); the verifier accepts both engines.

Also: chunk-parallel MSMs, precomputed blinding-generator table, indicator-
gated padding so pad coordinates are exact zeros and cost nothing, parallel
sumcheck rounds and twin derivation, a thread-local ChaCha CSPRNG for
blinding generation, and lock-discipline fixes (never hold a cache lock
across rayon work: a worker waiting on stolen subtasks can steal a job that
blocks on the same lock and deadlock the pool -- this also fixes a latent
hazard in the halo2 key caches).

Measured on the 4-core benchmark host (steady state; v4 has no keygen so
first proofs cost the same):
- 768x2x256: prove 81 s warm / 429 s cold -> 97 ms (835x / 4423x), verify
  1.3 s -> 101 ms
- 16x2x16: prove 7.0 s -> 21 ms (333x); 64x4x64: 10.8 s -> 35 ms (309x)
- 768x4x768 and 768x4x2304 (OOM-infeasible under v3): 183 ms and 470 ms
- end-to-end pipeline: 16x2x16 x8 71.4 s -> 0.098 s (729x); 32x4x32 x6
  107.4 s -> 0.097 s (1107x)
- proving memory: gigabytes -> tens of megabytes
Full README quickstart flow validated on distilgpt2 with the
ng0-k1/distilgpt2-finetuned-es adapter (six rank-16 768x2304 c_attn
modules): 60 invocation proofs generated in 62s and verified in 162s on a
4-core host, with tampered artifacts rejected. Under v3 a single module of
this shape was estimated at >200GB of proving material.
The prover recomputed the adapter commitment (serde + SHA-256 over a
multi-MB core) on every invocation proof, and batch verification re-parsed
the same setup JSON for every artifact of a module. Both are now cached by
content hash, keeping per-proof costs flat in adapter size on both sides.
Hiding (high): blinding factors for adapter row/weight commitments were
derived from (salt, domain, index) only, so two same-shaped adapters from
one contributor shared blindings at every index and commitment differences
C1_i - C2_i = (w1_i - w2_i)*G leaked exact weight differences from public
manifests. Blindings are now derived under a per-adapter key (keyed BLAKE3
of the full canonical adapter payload under the salt), so distinct adapters
never share blindings; a regression test pins this.

Salt handling (medium): adapter_salt() is now keyed by the resolved salt
file path instead of a single process global (a changed
ZKLORA_ADAPTER_SALT_FILE takes effect immediately), and file creation is
atomic (O_EXCL + temp-file rename) so concurrent first-touch processes
converge on one salt. LoRAServer warns instead of silently sharing when the
env var already points at another server's salt file.

Legacy compatibility (medium): the claim that v3 halo2 artifacts (schema 2,
backend zklora-halo2-v3 -- the id main actually emits) still verify is now
backed by a test that reconstructs artifacts byte-for-byte as main wrote
them and runs them through verify_artifacts, including tamper rejection.

Robustness: duplicate invocation records are rejected before parallel proof
generation can race on artifact paths; invocation-proof deserialization is
allocation-bounded by input size; explicit dimension caps stop hostile
statements from driving unbounded allocations; the LogUp engine absorbs the
entry commitments into its own transcript (defense in depth for the
recomposition challenge); verifier rejects nonzero pad responses, making
the pad-coordinate invariant checked rather than conventional; limb_plan
now enforces the documented at-most-+1 multi-limb slack instead of merely
claiming it, with the general slack formula documented.

Also fixes a stale sumcheck comment and documents the LoRAServer last_*
snapshot-slot locking invariant.
@BidhanRoy
BidhanRoy force-pushed the claude/lora-1000x-perf-a0p7ar branch from e447387 to 166aefe Compare June 11, 2026 18:02
@BidhanRoy BidhanRoy closed this Jun 11, 2026
@BidhanRoy
BidhanRoy deleted the claude/lora-1000x-perf-a0p7ar branch June 11, 2026 18:03
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