Skip to content

Commit bd6a81e

Browse files
committed
perf: pre-compute alpha powers to eliminate E×E multiply per constraint
Replace on-the-fly alpha power generation (alpha_power *= alpha, one E×E multiply per constraint per LDE point) with pre-computed alpha power lookup (one index increment per constraint). For CPU table with 66 constraints × ~500K LDE points, this eliminates ~33M E×E multiplies. Also updates VerifierBuilder to use pre-computed transition_coeffs directly as alpha powers.
1 parent ea69e3c commit bd6a81e

3 files changed

Lines changed: 37 additions & 32 deletions

File tree

crypto/stark/src/air_builder.rs

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,10 @@ pub struct ProverBuilder<'a, F: IsSubFieldOf<E> + IsFFTField, E: IsField> {
5555
step_size: usize,
5656
num_rows: usize,
5757
accumulator: FieldElement<E>,
58-
alpha: FieldElement<E>,
59-
alpha_power: FieldElement<E>,
58+
/// Pre-computed composition alpha powers [1, α, α², ...].
59+
/// Indexed by constraint_idx to avoid E×E multiply per constraint.
60+
alpha_powers: &'a [FieldElement<E>],
61+
constraint_idx: usize,
6062
rap_challenges: &'a [FieldElement<E>],
6163
logup_alpha_powers: &'a [FieldElement<E>],
6264
logup_table_offset_val: &'a FieldElement<E>,
@@ -73,7 +75,7 @@ where
7375
pub fn new(
7476
lde_trace: &'a LDETraceTable<F, E>,
7577
row: usize,
76-
alpha: &FieldElement<E>,
78+
alpha_powers: &'a [FieldElement<E>],
7779
rap_challenges: &'a [FieldElement<E>],
7880
logup_alpha_powers: &'a [FieldElement<E>],
7981
logup_table_offset: &'a FieldElement<E>,
@@ -84,8 +86,8 @@ where
8486
step_size: lde_trace.lde_step_size,
8587
num_rows: lde_trace.num_rows(),
8688
accumulator: FieldElement::zero(),
87-
alpha: alpha.clone(),
88-
alpha_power: FieldElement::one(),
89+
alpha_powers,
90+
constraint_idx: 0,
8991
rap_challenges,
9092
logup_alpha_powers,
9193
logup_table_offset_val: logup_table_offset,
@@ -101,7 +103,7 @@ where
101103
pub fn new_with_cache(
102104
lde_trace: &'a LDETraceTable<F, E>,
103105
row: usize,
104-
alpha: &FieldElement<E>,
106+
alpha_powers: &'a [FieldElement<E>],
105107
rap_challenges: &'a [FieldElement<E>],
106108
logup_alpha_powers: &'a [FieldElement<E>],
107109
logup_table_offset: &'a FieldElement<E>,
@@ -120,8 +122,8 @@ where
120122
step_size: lde_trace.lde_step_size,
121123
num_rows: lde_trace.num_rows(),
122124
accumulator: FieldElement::zero(),
123-
alpha: alpha.clone(),
124-
alpha_power: FieldElement::one(),
125+
alpha_powers,
126+
constraint_idx: 0,
125127
rap_challenges,
126128
logup_alpha_powers,
127129
logup_table_offset_val: logup_table_offset,
@@ -142,7 +144,6 @@ where
142144
#[inline]
143145
fn main(&self, offset: usize, col: usize) -> FieldElement<E> {
144146
if offset == 0 && !self.main_row_cache.is_empty() {
145-
// Use contiguous pre-fetched cache (avoids random column-major access)
146147
self.main_row_cache[col].clone().to_extension()
147148
} else {
148149
let lde_row = (self.row + offset * self.step_size) % self.num_rows;
@@ -158,8 +159,9 @@ where
158159

159160
#[inline]
160161
fn assert_zero(&mut self, expr: FieldElement<E>) {
161-
self.accumulator = &self.accumulator + &self.alpha_power * &expr;
162-
self.alpha_power = &self.alpha_power * &self.alpha;
162+
// Use pre-computed alpha power — no E×E multiply per constraint
163+
self.accumulator = &self.accumulator + &self.alpha_powers[self.constraint_idx] * &expr;
164+
self.constraint_idx += 1;
163165
}
164166

165167
fn challenge(&self, idx: usize) -> &FieldElement<E> {
@@ -187,17 +189,17 @@ where
187189

188190
#[inline]
189191
fn assert_zero_base(&mut self, expr: FieldElement<F>) {
190-
// F*E multiplication: 3 base muls (vs 6 for E*E)
191-
self.accumulator = &self.accumulator + &expr * &self.alpha_power;
192-
self.alpha_power = &self.alpha_power * &self.alpha;
192+
// F×E multiplication (3 base muls) with pre-computed alpha power (no E×E)
193+
self.accumulator = &self.accumulator + &expr * &self.alpha_powers[self.constraint_idx];
194+
self.constraint_idx += 1;
193195
}
194196
}
195197

196198
pub struct VerifierBuilder<'a, E: IsField> {
197199
frame: &'a Frame<E, E>,
198200
accumulator: FieldElement<E>,
199-
alpha: FieldElement<E>,
200-
alpha_power: FieldElement<E>,
201+
alpha_powers: &'a [FieldElement<E>],
202+
constraint_idx: usize,
201203
rap_challenges: &'a [FieldElement<E>],
202204
logup_alpha_powers: &'a [FieldElement<E>],
203205
logup_table_offset_val: &'a FieldElement<E>,
@@ -206,16 +208,16 @@ pub struct VerifierBuilder<'a, E: IsField> {
206208
impl<'a, E: IsField> VerifierBuilder<'a, E> {
207209
pub fn new(
208210
frame: &'a Frame<E, E>,
209-
alpha: &FieldElement<E>,
211+
alpha_powers: &'a [FieldElement<E>],
210212
rap_challenges: &'a [FieldElement<E>],
211213
logup_alpha_powers: &'a [FieldElement<E>],
212214
logup_table_offset: &'a FieldElement<E>,
213215
) -> Self {
214216
Self {
215217
frame,
216218
accumulator: FieldElement::zero(),
217-
alpha: alpha.clone(),
218-
alpha_power: FieldElement::one(),
219+
alpha_powers,
220+
constraint_idx: 0,
219221
rap_challenges,
220222
logup_alpha_powers,
221223
logup_table_offset_val: logup_table_offset,
@@ -246,8 +248,8 @@ impl<'a, E: IsField> AirBuilder<E> for VerifierBuilder<'a, E> {
246248

247249
#[inline]
248250
fn assert_zero(&mut self, expr: FieldElement<E>) {
249-
self.accumulator = &self.accumulator + &self.alpha_power * &expr;
250-
self.alpha_power = &self.alpha_power * &self.alpha;
251+
self.accumulator = &self.accumulator + &self.alpha_powers[self.constraint_idx] * &expr;
252+
self.constraint_idx += 1;
251253
}
252254

253255
fn challenge(&self, idx: usize) -> &FieldElement<E> {
@@ -275,9 +277,9 @@ impl<'a, E: IsField> MainAirBuilder<E, E> for VerifierBuilder<'a, E> {
275277

276278
#[inline]
277279
fn assert_zero_base(&mut self, expr: FieldElement<E>) {
278-
// E*E (same as assert_zero for verifier)
279-
self.accumulator = &self.accumulator + &self.alpha_power * &expr;
280-
self.alpha_power = &self.alpha_power * &self.alpha;
280+
// Use pre-computed alpha power (same indexing as assert_zero)
281+
self.accumulator = &self.accumulator + &self.alpha_powers[self.constraint_idx] * &expr;
282+
self.constraint_idx += 1;
281283
}
282284
}
283285

crypto/stark/src/constraints/evaluator.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,13 @@ where
102102
Vec::new()
103103
};
104104

105+
// Pre-compute composition alpha powers [1, α, α², ...] for ALL constraints.
106+
// This avoids an E×E multiply per constraint per LDE point in the hot loop.
107+
// Use a generous count: main constraints + LogUp aux constraints.
108+
// Over-allocating is cheap (one-time cost); under-allocating would panic.
109+
let total_constraints = air.num_transition_constraints() * 2 + 64;
110+
let composition_alpha_powers = compute_alpha_powers(composition_alpha, total_constraints);
111+
105112
let num_main_cols = lde_trace.num_main_cols();
106113
let use_dual_path = air.has_main_builder();
107114

@@ -116,7 +123,7 @@ where
116123
let mut builder = crate::air_builder::ProverBuilder::new_with_cache(
117124
lde_trace,
118125
i,
119-
composition_alpha,
126+
&composition_alpha_powers,
120127
rap_challenges,
121128
&logup_alpha_powers,
122129
logup_table_offset,
@@ -147,7 +154,7 @@ where
147154
let mut builder = crate::air_builder::ProverBuilder::new_with_cache(
148155
lde_trace,
149156
i,
150-
composition_alpha,
157+
&composition_alpha_powers,
151158
rap_challenges,
152159
&logup_alpha_powers,
153160
logup_table_offset,

crypto/stark/src/verifier.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -336,14 +336,10 @@ pub trait IsStarkVerifier<
336336
// AirBuilder path: fused alpha combination, then divide by uniform zerofier.
337337
// All constraints use the uniform zerofier Z(z) = z^n - 1 (period=1, offset=0,
338338
// end_exemptions=0), so we apply one inverse at the end.
339-
let alpha = if challenges.transition_coeffs.len() >= 2 {
340-
challenges.transition_coeffs[1].clone()
341-
} else {
342-
FieldElement::one()
343-
};
339+
// Use pre-computed transition coefficients as alpha powers
344340
let mut builder = VerifierBuilder::new(
345341
&ood_frame,
346-
&alpha,
342+
&challenges.transition_coeffs,
347343
&challenges.rap_challenges,
348344
&logup_alpha_powers,
349345
&logup_table_offset,

0 commit comments

Comments
 (0)