Skip to content

Commit 1163a37

Browse files
feat(sumcheck): [PR06] row-parallel compute_univariate via VectorField (WASM SIMD) (#186)
Introduce a row-parallel WASM-SIMD path for the sumcheck prover round. The round is written once against an `Element` template parameter that is either scalar `FF` (one trace row per iteration) or `VectorField<FF::Params>`, which packs 5 trace rows per SIMD lane on WASM-SIMD builds of opted-in flavors. Scalar is the width-1 special case -- `FF`'s `from_lanes`, `horizontal_sum`, and `convert_to` are identities -- so one code path serves both and element-type branches survive in only a few named helpers. Key points: - Element-generic accumulate path. `accumulate_edge_chunks<Element>` drives the main loop with `EDGE_STRIDE = 2 * lane_count<Element>` (1 for `FF`, 5 for `VectorField`). `for_each_edge_group` sweeps full SIMD groups then a scalar leftover-pair tail; `reduce_accumulator<Element>` horizontally sums lanes into the FF round accumulator (a plain add for `FF`). - Dispatch. `compute_univariate<Element = void>` resolves the element from an explicit per-flavor `USE_SIMD_SUMCHECK` opt-in, gated by `SupportsSimdSumcheck<Flavor> && simd_available_v<FF::Params> && !USES_ROW_MANIFEST`; `BB_FORCE_SCALAR_LANE` forces scalar (A/B benchmarks). Ultra/Mega opt in; AVM stays scalar (it proves natively, SIMD is WASM-only). Parity tests pass the concrete element explicitly. - VectorField primitives. `from_lanes` / `horizontal_sum` / `gather`, and `is_zero()` / `eq()` returning a bool with all-lanes semantics (per-lane mask forms live at `is_zero_mask` / `eq_mask`). The bool form is what the batched relation-skip and selector-gated subrelations require; the earlier raw-mask signature silently disabled gated subrelations on the SIMD path. - Edge gather / parameters. `LazyExtendedEdges` is generalized over the element type, so columns of relations that skip a row are never gathered; its cache is heap-backed for `VectorField` (~33 KB on Mega would overflow thin WASM worker stacks) and stays inline for `FF`. `RelationParameters::convert_to<Element>` broadcasts every parameter next to its declaration; `GateSeparatorPolynomial:: gather<Element>` does the stride-2 pow_beta read. - Tuning. `ROWS_PER_CHUNK = 50` on the SIMD path (5 lane-batches per chunk, no scalar tail). Correctness: bit-identical to the prior scalar path for both `FF` and `VectorField`; `RowParallelParity` tests green across Mega/MegaZK/Ultra/UltraZK. Performance: +5.02% aggregate chonk-prove (V8 / M3 Pro, threads = 8) across the 11 pinned flows. Co-authored-by: iakovenkos <sergey.s.yakovenko@gmail.com>
1 parent aa67845 commit 1163a37

18 files changed

Lines changed: 789 additions & 60 deletions

barretenberg/cpp/src/barretenberg/ecc/fields/field_declarations.hpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,14 @@ template <class Params_> struct alignas(32) field {
333333
BB_INLINE constexpr field sqr() const noexcept;
334334
BB_INLINE constexpr void self_sqr() & noexcept;
335335

336+
// Reducing a scalar field's single lane returns itself. Lets `field` substitute for `VectorField` as a
337+
// lane element type so per-lane reduction code (e.g. sumcheck's `reduce_accumulator`) needs no branch.
338+
BB_INLINE constexpr field horizontal_sum() const noexcept { return *this; }
339+
340+
// Width-1 counterpart of `VectorField::from_lanes`: a scalar field has one lane, so it just evaluates
341+
// `value_at` at lane 0. Lets `field` be gathered through the same lane-generic edge code as `VectorField`.
342+
template <typename Fn> BB_INLINE static field from_lanes(const Fn& value_at) noexcept { return value_at(0); }
343+
336344
BB_INLINE constexpr field pow(const uint256_t& exponent) const noexcept;
337345
BB_INLINE constexpr field pow(uint64_t exponent) const noexcept;
338346
// STARKNET: next line was commented as stark252 violates the assertion

barretenberg/cpp/src/barretenberg/ecc/fields/vector_field.bench.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ static void bench_vector_eq(State& state)
210210
volatile uint32_t sink = 0;
211211
for (auto _ : state) {
212212
for (int64_t it = 0; it < ITERATIONS; ++it) {
213-
sink += a.eq(b);
213+
sink += a.eq_mask(b);
214214
DoNotOptimize(sink);
215215
}
216216
}
@@ -225,7 +225,7 @@ static void bench_vector_is_zero(State& state)
225225
volatile uint32_t sink = 0;
226226
for (auto _ : state) {
227227
for (int64_t it = 0; it < ITERATIONS; ++it) {
228-
sink += a.is_zero();
228+
sink += a.is_zero_mask();
229229
DoNotOptimize(sink);
230230
}
231231
}

barretenberg/cpp/src/barretenberg/ecc/fields/vector_field.hpp

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@
5757

5858
namespace bb {
5959

60+
// `if constexpr`-friendly form of BB_VECTOR_FIELD_SIMD: lets dispatch sites stay templated instead of `#if`d.
61+
inline constexpr bool vector_field_simd_enabled = BB_VECTOR_FIELD_SIMD;
62+
6063
// ---------------------------------------------------------------------------
6164
// Compile-time constants derived from Params.
6265
// ---------------------------------------------------------------------------
@@ -129,7 +132,12 @@ inline constexpr bool simd_available_v = (BB_VECTOR_FIELD_SIMD != 0) && has_simd
129132
template <class Params> struct alignas(32) VectorField {
130133
using Field = field<Params>;
131134
using scalar_type = Field;
135+
// Relation algebra resolves View / CoefficientAccumulator / modulus off the element type, so
136+
// VectorField must mirror those names from `field<Params>` to drop in as the element type.
137+
using View = VectorField;
138+
using CoefficientAccumulator = VectorField;
132139
static constexpr size_t SIZE = 5;
140+
static constexpr auto modulus = Field::modulus;
133141

134142
// load_contiguous, store_contiguous, and the linear-memory ctor read/write
135143
// raw Field bytes at fixed offsets (+32, +48, ...). Catch any future drift
@@ -173,6 +181,31 @@ template <class Params> struct alignas(32) VectorField {
173181
// native).
174182
explicit VectorField(const std::array<Field, 5>& in) noexcept { store_from_array(in); }
175183

184+
// Implicit broadcast ctors -- mirror `field<Params>(int)`, `field<Params>(uint64_t)`, `field<Params>(Field)`
185+
// so generic relation code like `q_arith_m - FF(1)` or multiplying an accumulator by a loop-invariant scalar
186+
// constant (e.g. Poseidon round constants, typed as raw `fr`) compiles unchanged when FF=VectorField. The
187+
// scalar value is broadcast to every lane.
188+
VectorField(const Field& s) noexcept { *this = broadcast(s); }
189+
VectorField(int s) noexcept { *this = broadcast(Field(s)); }
190+
VectorField(uint64_t s) noexcept { *this = broadcast(Field(s)); }
191+
192+
static VectorField zero() noexcept { return broadcast(Field::zero()); }
193+
static VectorField one() noexcept { return broadcast(Field::one()); }
194+
195+
VectorField sqr() const noexcept { return (*this) * (*this); }
196+
void self_sqr() noexcept { *this = (*this) * (*this); }
197+
VectorField operator-() const noexcept { return zero() - *this; }
198+
// Per-lane scalar invert. Slow path -- for static-init constants and rare relation use. For batched
199+
// inversions of independent values use the K=5 batch-inversion pattern in ecc/groups/element_impl.hpp.
200+
VectorField invert() const noexcept
201+
{
202+
auto a = to_array();
203+
for (auto& v : a) {
204+
v = v.invert();
205+
}
206+
return VectorField(a);
207+
}
208+
176209
// Construct from 5 fields linear in memory (lane L = base[L] for L in
177210
// 0..4). This is the canonical construction path used by the
178211
// vectorised loop abstraction in place of `gather` — no random-access
@@ -193,6 +226,16 @@ template <class Params> struct alignas(32) VectorField {
193226
return out;
194227
}
195228

229+
Field horizontal_sum() const noexcept
230+
{
231+
const std::array<Field, SIZE> lanes = to_array();
232+
Field sum = lanes[0];
233+
for (size_t lane = 1; lane < SIZE; ++lane) {
234+
sum += lanes[lane];
235+
}
236+
return sum;
237+
}
238+
196239
// Test/debug helpers — both pack/unpack the full 5-lane payload. Do not
197240
// use in hot code. For element-wise access use `to_array()` once and read
198241
// the result; for write-back use `load_contiguous` / `store_contiguous`.
@@ -217,6 +260,22 @@ template <class Params> struct alignas(32) VectorField {
217260
// the declaration is left unconstrained and callers route unsupported Params through the scalar path.
218261
VectorField operator*(const VectorField& other) const noexcept;
219262

263+
VectorField& operator+=(const VectorField& other) noexcept
264+
{
265+
*this = (*this) + other;
266+
return *this;
267+
}
268+
VectorField& operator-=(const VectorField& other) noexcept
269+
{
270+
*this = (*this) - other;
271+
return *this;
272+
}
273+
VectorField& operator*=(const VectorField& other) noexcept
274+
{
275+
*this = (*this) * other;
276+
return *this;
277+
}
278+
220279
// SLOW PATH — for random-access patterns only. For contiguous loads/stores
221280
// use `load_contiguous` / `store_contiguous`, which avoid the per-lane
222281
// scalar pack/unpack and dispatch a single SIMD shuffle.
@@ -312,8 +371,15 @@ template <class Params> struct alignas(32) VectorField {
312371
}
313372

314373
// Returns a 5-bit mask: bit 0 = scalar, bits 1..4 = quad lanes 0..3.
315-
uint32_t eq(const VectorField& other) const noexcept;
316-
uint32_t is_zero() const noexcept;
374+
uint32_t eq_mask(const VectorField& other) const noexcept;
375+
uint32_t is_zero_mask() const noexcept;
376+
377+
// Bool form: true iff EVERY lane satisfies the predicate. Prover relation gates coerce these to bool
378+
// (e.g. `if (selector.is_zero()) skip`); a partially-non-zero lane pattern must read as not-zero, or
379+
// the gate skips a subrelation that should fire on at least one of the rows packed into the lanes.
380+
// The `_mask` form stays available for tests/microbenches that need per-lane visibility.
381+
bool eq(const VectorField& other) const noexcept { return eq_mask(other) == 0b11111u; }
382+
bool is_zero() const noexcept { return is_zero_mask() == 0b11111u; }
317383

318384
private:
319385
void store_from_array(const std::array<Field, 5>& in) noexcept;
@@ -1035,7 +1101,7 @@ template <class Params>
10351101
// a 4-lane all-ones/zero compare into a 4-bit integer mask in one instruction.
10361102

10371103
template <class Params>
1038-
[[gnu::always_inline]] inline uint32_t VectorField<Params>::eq(const VectorField& other) const noexcept
1104+
[[gnu::always_inline]] inline uint32_t VectorField<Params>::eq_mask(const VectorField& other) const noexcept
10391105
{
10401106
const VectorField d = (*this) - other;
10411107

@@ -1119,7 +1185,7 @@ template <class Params>
11191185
return scalar_eq | (lanes_eq << 1);
11201186
}
11211187

1122-
template <class Params> [[gnu::always_inline]] inline uint32_t VectorField<Params>::is_zero() const noexcept
1188+
template <class Params> [[gnu::always_inline]] inline uint32_t VectorField<Params>::is_zero_mask() const noexcept
11231189
{
11241190
// Same pattern as eq, but on (*this) directly (no subtract).
11251191
uint64_t sacc_z = scalar_data[0];
@@ -1316,7 +1382,7 @@ inline VectorField<Params> VectorField<Params>::operator*(const VectorField& oth
13161382
return r;
13171383
}
13181384

1319-
template <class Params> inline uint32_t VectorField<Params>::eq(const VectorField& other) const noexcept
1385+
template <class Params> inline uint32_t VectorField<Params>::eq_mask(const VectorField& other) const noexcept
13201386
{
13211387
uint32_t m = 0;
13221388
for (size_t i = 0; i < 5; ++i) {
@@ -1327,7 +1393,7 @@ template <class Params> inline uint32_t VectorField<Params>::eq(const VectorFiel
13271393
return m;
13281394
}
13291395

1330-
template <class Params> inline uint32_t VectorField<Params>::is_zero() const noexcept
1396+
template <class Params> inline uint32_t VectorField<Params>::is_zero_mask() const noexcept
13311397
{
13321398
uint32_t m = 0;
13331399
for (size_t i = 0; i < 5; ++i) {

barretenberg/cpp/src/barretenberg/ecc/fields/vector_field.test.cpp

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,19 @@ TEST(VectorFieldTest, EqualityDetectsMatchesAndMismatches)
9898

9999
// Same values — all 5 bits set.
100100
Vec vb(a);
101-
EXPECT_EQ(va.eq(vb), 0b11111u);
101+
EXPECT_EQ(va.eq_mask(vb), 0b11111u);
102102

103103
// Flip lane 0: bit 0 clears.
104104
auto a2 = a;
105105
a2[0] = a2[0] + fr(1);
106106
Vec vc(a2);
107-
EXPECT_EQ(va.eq(vc), 0b11110u);
107+
EXPECT_EQ(va.eq_mask(vc), 0b11110u);
108108

109109
// Flip lane 3: bit 3 clears.
110110
auto a3 = a;
111111
a3[3] = a3[3] + fr(1);
112112
Vec vd(a3);
113-
EXPECT_EQ(va.eq(vd), 0b10111u);
113+
EXPECT_EQ(va.eq_mask(vd), 0b10111u);
114114
}
115115

116116
TEST(VectorFieldTest, EqualityAcceptsAliasedCoarseRepresentations)
@@ -131,7 +131,7 @@ TEST(VectorFieldTest, EqualityAcceptsAliasedCoarseRepresentations)
131131

132132
Vec va(a);
133133
Vec vb(a_plus_p);
134-
EXPECT_EQ(va.eq(vb), 0b11111u);
134+
EXPECT_EQ(va.eq_mask(vb), 0b11111u);
135135
}
136136

137137
TEST(VectorFieldTest, IsZeroDetectsZeroAndP)
@@ -144,7 +144,7 @@ TEST(VectorFieldTest, IsZeroDetectsZeroAndP)
144144
vals[4] = fr::zero();
145145

146146
Vec v(vals);
147-
uint32_t iz = v.is_zero();
147+
uint32_t iz = v.is_zero_mask();
148148
// Lanes 0, 2, 4 should be zero; lanes 1, 3 non-zero.
149149
EXPECT_EQ(iz & 1u, 1u);
150150
EXPECT_EQ(iz & 2u, 0u);
@@ -162,7 +162,7 @@ TEST(VectorFieldTest, IsZeroAcceptsAliasedZero)
162162
bb::Bn254FrParams::modulus_3 };
163163
std::array<fr, 5> vals{ fr::zero(), p_as_field, fr::zero(), p_as_field, fr::one() };
164164
Vec v(vals);
165-
uint32_t iz = v.is_zero();
165+
uint32_t iz = v.is_zero_mask();
166166
EXPECT_EQ(iz, 0b01111u);
167167
}
168168

@@ -620,12 +620,12 @@ TEST(VectorFieldFqTest, EqualityDetectsMatchesAndMismatches)
620620
VecFq va(a);
621621

622622
VecFq vb(a);
623-
EXPECT_EQ(va.eq(vb), 0b11111u);
623+
EXPECT_EQ(va.eq_mask(vb), 0b11111u);
624624

625625
auto a_flipped = a;
626626
a_flipped[0] = a[0] + fq::one();
627627
VecFq vc(a_flipped);
628-
EXPECT_EQ(va.eq(vc), 0b11110u);
628+
EXPECT_EQ(va.eq_mask(vc), 0b11110u);
629629
}
630630

631631
TEST(VectorFieldFqTest, IsZeroDetectsZeroAndP)
@@ -635,12 +635,12 @@ TEST(VectorFieldFqTest, IsZeroDetectsZeroAndP)
635635
x = fq::zero();
636636
}
637637
VecFq v_zero(zeros);
638-
EXPECT_EQ(v_zero.is_zero(), 0b11111u);
638+
EXPECT_EQ(v_zero.is_zero_mask(), 0b11111u);
639639

640640
auto non_zero = random_five_fq();
641641
non_zero[0] = fq::one();
642642
VecFq v_nz(non_zero);
643-
EXPECT_EQ(v_nz.is_zero(), 0u);
643+
EXPECT_EQ(v_nz.is_zero_mask(), 0u);
644644
}
645645

646646
TEST(VectorFieldFqTest, DistributivityMulOverAdd)
@@ -677,6 +677,76 @@ TEST(VectorFieldFqTest, ScalarTypeAlias)
677677
SUCCEED();
678678
}
679679

680+
// `Vec` as a drop-in for `field<Params>` in templated relations / Univariate<Vec, K>: the static
681+
// identities, scalar-broadcast ctors, sqr, and lane-wise invert.
682+
683+
TEST(VectorFieldTest, OneIsAllOnes)
684+
{
685+
auto a = Vec::one().to_array();
686+
for (size_t k = 0; k < 5; ++k) {
687+
EXPECT_EQ(a[k], fr::one()) << "lane " << k;
688+
}
689+
}
690+
691+
TEST(VectorFieldTest, ZeroIsAllZeros)
692+
{
693+
auto a = Vec::zero().to_array();
694+
for (size_t k = 0; k < 5; ++k) {
695+
EXPECT_TRUE(a[k].is_zero()) << "lane " << k;
696+
}
697+
}
698+
699+
TEST(VectorFieldTest, ScalarBroadcastCtors)
700+
{
701+
fr s = fr::random_element();
702+
auto a = Vec(s).to_array();
703+
for (size_t k = 0; k < 5; ++k) {
704+
EXPECT_EQ(a[k], s) << "lane " << k;
705+
}
706+
707+
auto b = Vec(-2).to_array();
708+
fr neg2 = fr(-2);
709+
for (size_t k = 0; k < 5; ++k) {
710+
EXPECT_EQ(b[k], neg2) << "lane " << k;
711+
}
712+
713+
auto c = Vec(uint64_t{ 42 }).to_array();
714+
fr forty_two = fr(uint64_t{ 42 });
715+
for (size_t k = 0; k < 5; ++k) {
716+
EXPECT_EQ(c[k], forty_two) << "lane " << k;
717+
}
718+
}
719+
720+
TEST(VectorFieldTest, SqrMatchesSelfMul)
721+
{
722+
for (int trial = 0; trial < 16; ++trial) {
723+
auto a = random_five();
724+
std::array<fr, 5> expected;
725+
for (size_t k = 0; k < 5; ++k) {
726+
expected[k] = a[k] * a[k];
727+
}
728+
auto got = Vec(a).sqr().to_array();
729+
EXPECT_TRUE(field_array_eq(expected, got)) << "trial " << trial;
730+
}
731+
}
732+
733+
TEST(VectorFieldTest, InvertLanewise)
734+
{
735+
auto a = random_five();
736+
std::array<fr, 5> expected;
737+
for (size_t k = 0; k < 5; ++k) {
738+
expected[k] = a[k].invert();
739+
}
740+
auto got = Vec(a).invert().to_array();
741+
EXPECT_TRUE(field_array_eq(expected, got));
742+
743+
// x * x.invert() == 1 lane-wise.
744+
auto prod = (Vec(a) * Vec(a).invert()).to_array();
745+
for (size_t k = 0; k < 5; ++k) {
746+
EXPECT_EQ(prod[k], fr::one()) << "lane " << k;
747+
}
748+
}
749+
680750
// Contiguous/gather transposes for Fq — the MSM production path (g1 coordinates are Fq),
681751
// mirroring the Fr round-trips above.
682752
TEST(VectorFieldFqTest, LinearMemoryCtorAndStoreToRoundTrip)

barretenberg/cpp/src/barretenberg/flavor/flavor_concepts.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@ concept isMultilinearBatchingFlavor = IsMultilinearBatchingFlavorImpl<T>::value
6868
// flavors that don't declare the flag default to the serial loop.
6969
template <typename T> concept ParallelizesRelationBatching = requires { requires T::PARALLELIZE_RELATION_BATCHING; };
7070

71+
// Sumcheck-prover opt-in: a flavor declares USE_SIMD_SUMCHECK = true to route compute_univariate through the
72+
// row-parallel SimdLane, which packs several trace rows into each SIMD lane and evaluates the relation set with
73+
// VectorField as the element type. Flavors that don't declare the flag default to the scalar lane. Requires a
74+
// Relations_<T> template so the relation set can re-instantiate over the lane element. Whether the SIMD path is
75+
// actually taken is gated at dispatch on simd_available_v<FF::Params> (build target + field capability), so on
76+
// native or non-SIMD fields these flavors still run scalar; see SumcheckProverRound::compute_univariate.
77+
template <typename T>
78+
concept SupportsSimdSumcheck =
79+
requires { requires T::USE_SIMD_SUMCHECK; } && requires { typename T::template Relations_<typename T::FF>; };
80+
7181
// Short-monomial flavors that expose the codegen'd array layout -- a `Generated` member (the generated
7282
// relation/entity definitions) together with an EntityId-keyed ProverUnivariates -- i.e. native Ultra/Mega.
7383
// The sumcheck prover materializes their edges lazily per column. ECCVM/Translator are short-monomial but lack the

barretenberg/cpp/src/barretenberg/flavor/mega_app_flavor.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ class MegaAppFlavor : public MegaAppFlavor_Generated {
4444

4545
static constexpr size_t VIRTUAL_LOG_N = CONST_FOLDING_LOG_N;
4646
static constexpr bool USE_SHORT_MONOMIALS = true;
47+
// opt in to the row-parallel (SIMD) sumcheck path; see SupportsSimdSumcheck in flavor_concepts.hpp
48+
static constexpr bool USE_SIMD_SUMCHECK = true;
4749
static constexpr bool HasZK = false;
4850
static constexpr bool USE_PADDING = true;
4951
static constexpr size_t NUM_WIRES = CircuitBuilder::NUM_WIRES;

barretenberg/cpp/src/barretenberg/flavor/mega_avm_flavor.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ class MegaAvmFlavor : public MegaAVMFlavor_Generated {
4343

4444
static constexpr size_t VIRTUAL_LOG_N = MEGA_AVM_LOG_N;
4545
static constexpr bool USE_SHORT_MONOMIALS = true;
46+
// No USE_SIMD_SUMCHECK: the SIMD sumcheck path is WASM-only today and the AVM proves natively. Opt in
47+
// here once native SIMD field operations exist and can be routed through sumcheck's relation evaluation.
4648
static constexpr bool HasZK = false;
4749
static constexpr bool USE_PADDING = true;
4850
static constexpr size_t NUM_WIRES = CircuitBuilder::NUM_WIRES;

barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class MegaFlavor : public MegaFlavor_Generated {
4545
static constexpr size_t VIRTUAL_LOG_N = CONST_FOLDING_LOG_N;
4646
// indicates when evaluating sumcheck, edges can be left as degree-1 monomials
4747
static constexpr bool USE_SHORT_MONOMIALS = true;
48+
// opt in to the row-parallel (SIMD) sumcheck path; see SupportsSimdSumcheck in flavor_concepts.hpp
49+
static constexpr bool USE_SIMD_SUMCHECK = true;
4850
// Indicates that this flavor runs with non-ZK Sumcheck.
4951
static constexpr bool HasZK = false;
5052
// To achieve fixed proof size and that the recursive verifier circuit is constant, we are using padding in Sumcheck

barretenberg/cpp/src/barretenberg/flavor/mega_kernel_flavor.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ class MegaKernelFlavor : public MegaKernelFlavor_Generated {
4545

4646
static constexpr size_t VIRTUAL_LOG_N = CONST_FOLDING_LOG_N;
4747
static constexpr bool USE_SHORT_MONOMIALS = true;
48+
// opt in to the row-parallel (SIMD) sumcheck path; see SupportsSimdSumcheck in flavor_concepts.hpp
49+
static constexpr bool USE_SIMD_SUMCHECK = true;
4850
static constexpr bool HasZK = false;
4951
static constexpr bool USE_PADDING = true;
5052
static constexpr size_t NUM_WIRES = CircuitBuilder::NUM_WIRES;

0 commit comments

Comments
 (0)