finalize_constants: Feed all M31 limbs into find_max_consecutive - #604
finalize_constants: Feed all M31 limbs into find_max_consecutive#604ilyalesokhin-starkware wants to merge 1 commit into
Conversation
PR SummaryMedium Risk Overview
New tests cover the helper’s gap/min_base semantics and end-to-end cases where QM31 limbs lengthen the chain or leave limbs past a gap on decomposition. Reviewed by Cursor Bugbot for commit ae754f4. Bugbot is set up for automated code reviews on this repo. Configure here. |
dancarmoz
left a comment
There was a problem hiding this comment.
@dancarmoz made 1 comment.
Reviewable status: 0 of 2 files reviewed, 1 unresolved discussion (waiting on ilyalesokhin-starkware).
crates/circuits/src/finalize_constants_test.rs line 219 at r1 (raw file):
let mut context = TraceContext::default(); // A single QM31 constant whose limbs (2, 3, 4, 5) fill the consecutive prefix above // `min_base = 2`. No *pure* M31 constant requires 3, 4 or 5, but because the limbs are fed into
I think this works because 0 and 1 are added as constants globally, so they fill the gap up 2 (and then the limbs 2,3,4,5 fill the rest up to 5). So this has nothing to with min_base = 2. Right?
If this code had min_base = 3 and the constant was (3, 4, 5, 6), then I believe the base would have been 3, not 6, because find_max_consecutive would have detected a gap at 2, so it would return 1, and then the max would be 3.
If the preferred logic in this case would be to go up to 6 (which I think makes sense), maybe we should just feed min_base into find_max_consecutive and have the loop start from it instead of from 0?
b209789 to
8633e56
Compare
ilyalesokhin-starkware
left a comment
There was a problem hiding this comment.
@ilyalesokhin-starkware made 1 comment and resolved 1 discussion.
Reviewable status: 0 of 2 files reviewed, all discussions resolved (waiting on dancarmoz).
crates/circuits/src/finalize_constants_test.rs line 219 at r1 (raw file):
Previously, dancarmoz (Dan Carmon) wrote…
I think this works because
0and1are added as constants globally, so they fill the gap up 2 (and then the limbs 2,3,4,5 fill the rest up to 5). So this has nothing to withmin_base = 2. Right?
If this code hadmin_base = 3and the constant was(3, 4, 5, 6), then I believe the base would have been 3, not 6, becausefind_max_consecutivewould have detected a gap at 2, so it would return 1, and then the max would be 3.
If the preferred logic in this case would be to go up to 6 (which I think makes sense), maybe we should just feedmin_baseintofind_max_consecutiveand have the loop start from it instead of from 0?
done
8633e56 to
980cb46
Compare
dancarmoz
left a comment
There was a problem hiding this comment.
@dancarmoz reviewed 2 files and all commit messages, and made 2 comments.
Reviewable status: all files reviewed, 2 unresolved discussions (waiting on ilyalesokhin-starkware).
-- commits line 13 at r3:
I don't think it's critical but the commit message does not accurately document the new changes? i.e. the function now walks up not from 0 but from the min_base argument.
crates/circuits/src/finalize_constants.rs line 150 at r3 (raw file):
// M31 limbs are all < 2^31, so `n + 1` never overflows a u32. let mut n = min_base; while m31_values.contains(&(n as u32 + 1)) {
Might be nicer if n is a u32 variable and converted to usize at the end (or maybe even later) rather than in every iteration?
Size the decomposition base from the consecutive run of *all* required M31 values, including the limbs of every CM31/QM31 constant (not just pure `(x, 0, 0, 0)` constants). Each such limb is eventually built via the `+1` chain or `build_m31_from_base`, so a limb that extends the run above `min_base` is produced by the chain for free instead of via base decomposition. Implements the existing TODO. The limb set is collected in the existing population pass (no extra iteration), and `find_max_consecutive` now takes a `&HashSet<u32>` and walks up from `min_base` in O(M) instead of sorting in O(M log M). This only affects the chosen base; the algorithm is correct for any base >= 2 (every limb is < base and covered by the chain), so it is a gate-count optimization, not a soundness change. Adds unit tests for `find_max_consecutive` and an integration test showing QM31 limbs grow the `+1` chain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
980cb46 to
ae754f4
Compare
|
Previously, dancarmoz (Dan Carmon) wrote…
changed the base type to u32. |
ilyalesokhin-starkware
left a comment
There was a problem hiding this comment.
@ilyalesokhin-starkware made 1 comment and resolved 2 discussions.
Reviewable status: 0 of 2 files reviewed, all discussions resolved (waiting on dancarmoz).
Previously, dancarmoz (Dan Carmon) wrote…
I don't think it's critical but the commit message does not accurately document the new changes? i.e. the function now walks up not from 0 but from the
min_baseargument.
done

What
Implements the long-standing TODO in
finalize_constants_with_min_base:The decomposition base is now sized from the consecutive prefix
[0, N]of all required M31 values — the keys ofm31_constantsplus the M31 limbs of every CM31/QM31 constant — rather than only the pure(x, 0, 0, 0)constants.Each such limb is eventually built (via the
+1chain orbuild_m31_from_base), so a limb that extends the prefix is now produced by the chain for free instead of via base decomposition.How
HashSet<u32>— no extra iteration over the constants.find_max_consecutivenow takes a&HashSet<u32>and walks up from 0 in O(M), replacing the previous O(M log M) sort+dedup. (Dropped the now-unuseditertoolsimport.)Why it's safe
This change only affects the chosen
base. The rest of the algorithm is correct for anybase >= 2(every limb is< baseand covered by the+1chain), so this is a gate-count optimization, not a soundness change. Adding values to the set is monotonic — the base can only grow or stay the same.Tests
test_find_max_consecutive— gap-at-1, gap-after-zero, full run, large-values-past-gap.test_find_max_consecutive_without_zero_panics— the zero precondition.test_qm31_limbs_extend_plus_one_chain— constant(2,3,4,5)withmin_base=2; its limbs grow the base to 5 so the+1chain runs2..=5, verified via the circuit topology and carried values.All
circuitstests (73) pass;circuit-commoncompiles; clippy + rustfmt clean.Note for reviewers
This changes the base chosen for real circuits (
finalize_contextusesmin_base=256), so a constant whose limbs form a consecutive run past 256 will enlarge the+1chain and shift trace/proof sizes. Worth a fullcargo test --releaseif there are proof/trace-size fixtures I didn't run.🤖 Generated with Claude Code