Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
sumcheck_iteration.bench.cpp
Go to the documentation of this file.
24
25#include <benchmark/benchmark.h>
26
27#include <algorithm>
28#include <atomic>
29#include <cstdlib>
30#include <numeric>
31#include <string>
32#include <string_view>
33#include <vector>
34
35namespace {
36
37using namespace bb;
38
39struct ActiveBlock {
40 size_t start = 0;
41 size_t size = 0;
42};
43
44std::vector<ActiveBlock> make_active_blocks(const size_t round_size, const bool fragmented)
45{
46 if (!fragmented) {
47 return { ActiveBlock{ .start = 0, .size = round_size } };
48 }
49
50 // Translator-like round-0 shape seen in Chonk: roughly 65% of edge-pairs are active,
51 // split across a small number of ranges from concatenated mini-circuit wires plus tails.
52 constexpr size_t NUM_BLOCKS = 18;
53 constexpr size_t ACTIVE_PERCENT = 65;
54 const size_t total_pairs = round_size / 2;
55 const size_t active_pairs = std::max<size_t>(NUM_BLOCKS, total_pairs * ACTIVE_PERCENT / 100);
56 const size_t inactive_pairs = total_pairs - active_pairs;
57 const size_t base_block_pairs = active_pairs / NUM_BLOCKS;
58 const size_t extra_block_pairs = active_pairs % NUM_BLOCKS;
59 const size_t base_gap_pairs = inactive_pairs / NUM_BLOCKS;
60 const size_t extra_gap_pairs = inactive_pairs % NUM_BLOCKS;
61
63 blocks.reserve(NUM_BLOCKS);
64 size_t pair_cursor = 0;
65 for (size_t block_idx = 0; block_idx < NUM_BLOCKS; ++block_idx) {
66 const size_t gap = base_gap_pairs + (block_idx < extra_gap_pairs ? 1 : 0);
67 pair_cursor += gap;
68 const size_t block_pairs = base_block_pairs + (block_idx < extra_block_pairs ? 1 : 0);
69 blocks.push_back(ActiveBlock{ .start = pair_cursor * 2, .size = block_pairs * 2 });
70 pair_cursor += block_pairs;
71 }
72 return blocks;
73}
74
75template <typename FF> std::vector<FF> make_challenges(const size_t log_n)
76{
77 std::vector<FF> result;
78 result.reserve(log_n);
79 for (size_t idx = 0; idx < log_n; ++idx) {
80 result.emplace_back(static_cast<uint64_t>(idx + 7));
81 }
82 return result;
83}
84
85template <typename Flavor> class SyntheticPolynomials {
86 public:
87 using FF = typename Flavor::FF;
90
91 explicit SyntheticPolynomials(const size_t size)
92 {
93 storage.reserve(Flavor::NUM_ALL_ENTITIES);
94 for (size_t poly_idx = 0; poly_idx < Flavor::NUM_ALL_ENTITIES; ++poly_idx) {
95 storage.emplace_back(size);
96 const FF value = FF(static_cast<uint64_t>(poly_idx + 3));
97 for (auto& coeff : storage.back().coeffs()) {
98 coeff = value;
99 }
100 }
101 for (auto [prover_poly, stored_poly] : zip_view(polynomials.get_all(), storage)) {
102 prover_poly = stored_poly.share();
103 }
104 if constexpr (requires(ProverPolynomials& p) { p.row_skip_active_prefix_end = size_t{}; }) {
105 polynomials.row_skip_active_prefix_end = size;
106 }
107 }
108
109 ProverPolynomials polynomials;
110
111 private:
113};
114
115template <typename Flavor, typename Edges> void fill_extended_edges(Edges& edges)
116{
117 using FF = typename Flavor::FF;
118 size_t entity_idx = 0;
119 for (auto& edge : edges.get_all()) {
120 const FF value = FF(static_cast<uint64_t>(entity_idx + 11));
121 for (auto& evaluation : edge.evaluations) {
122 evaluation = value;
123 }
124 ++entity_idx;
125 }
126}
127
128template <typename Flavor> auto make_relation_parameters()
129{
130 using FF = typename Flavor::FF;
132 params.eta = FF(17);
133 params.eta_two = FF(19);
134 params.eta_three = FF(23);
135 params.beta = FF(29);
136 params.gamma = FF(31);
137 params.public_input_delta = FF(37);
138 params.eccvm_set_permutation_delta = FF(43);
139 return params;
140}
141
142template <typename Flavor> auto make_subrelation_separators()
143{
144 using FF = typename Flavor::FF;
146 for (size_t idx = 0; idx < alphas.size(); ++idx) {
147 alphas[idx] = FF(static_cast<uint64_t>(idx + 101));
148 }
149 return alphas;
150}
151
152template <typename Flavor>
153void accumulate_one_edge(SumcheckProverRound<Flavor>& round,
155 const auto& extended_edges,
156 const bb::RelationParameters<typename Flavor::FF>& relation_parameters,
157 const typename Flavor::FF& scaling_factor)
158{
159 round.accumulate_relation_univariates_public(accum, extended_edges, relation_parameters, scaling_factor);
160}
161
162template <typename Flavor> void bench_accumulate_relations_only(benchmark::State& state)
163{
164 using FF = typename Flavor::FF;
165 using Round = SumcheckProverRound<Flavor>;
166 typename Round::SumcheckTupleOfTuplesOfUnivariates accum{};
167 typename Round::ExtendedEdges edges;
168 fill_extended_edges<Flavor>(edges);
169 auto params = make_relation_parameters<Flavor>();
170 Round round(/*initial_round_size=*/2);
171 const FF scaling_factor = FF(5);
172
173 for (auto _ : state) {
174 accumulate_one_edge<Flavor>(round, accum, edges, params, scaling_factor);
175 benchmark::DoNotOptimize(accum);
176 }
177
178 state.counters["relations"] = static_cast<double>(Flavor::NUM_RELATIONS);
179 state.counters["subrelations"] = static_cast<double>(Flavor::NUM_SUBRELATIONS);
180}
181
182enum class Scheduler {
183 STATIC_BLOCKS,
184 CHUNK_STEALING,
185};
186
187template <typename Flavor>
188void bench_sumcheck_loop_shape(benchmark::State& state, const Scheduler scheduler, const bool fragmented)
189{
190 using FF = typename Flavor::FF;
191 using Round = SumcheckProverRound<Flavor>;
192 using Tuple = typename Round::SumcheckTupleOfTuplesOfUnivariates;
193
194 const size_t log_n = static_cast<size_t>(state.range(0));
195 const size_t round_size = size_t{ 1 } << log_n;
196 constexpr size_t ROWS_PER_CHUNK = 64;
197
198 SyntheticPolynomials<Flavor> synthetic_polynomials(round_size);
199 auto& polynomials = synthetic_polynomials.polynomials;
200 auto params = make_relation_parameters<Flavor>();
201 const auto blocks = make_active_blocks(round_size, fragmented);
202
203 for (auto _ : state) {
204 Round round(round_size);
205 std::vector<Tuple> thread_accumulators(get_num_cpus());
206
207 if (scheduler == Scheduler::STATIC_BLOCKS) {
208 parallel_for([&](ThreadChunk chunk) {
209 typename Round::ExtendedEdges extended_edges;
210 for (const auto& block : blocks) {
211 const size_t iterations = block.size / 2;
212 for (size_t i : chunk.range(iterations)) {
213 const size_t edge_idx = block.start + i * 2;
214 round.extend_edges(extended_edges, polynomials, edge_idx);
215 accumulate_one_edge<Flavor>(
216 round, thread_accumulators[chunk.thread_index], extended_edges, params, FF(7));
217 }
218 }
219 });
220 } else {
222 for (const auto& block : blocks) {
223 for (size_t start = block.start; start < block.start + block.size; start += ROWS_PER_CHUNK) {
224 chunks.push_back(ActiveBlock{
225 .start = start,
226 .size = std::min(ROWS_PER_CHUNK, block.start + block.size - start),
227 });
228 }
229 }
230
231 std::atomic<size_t> next_chunk{ 0 };
232 const size_t num_slots = std::min(get_num_cpus(), std::max<size_t>(chunks.size(), 1));
233 thread_accumulators.resize(num_slots);
234 parallel_for(num_slots, [&](size_t slot_idx) {
235 typename Round::ExtendedEdges extended_edges;
236 while (true) {
237 const size_t chunk_idx = next_chunk.fetch_add(1, std::memory_order_relaxed);
238 if (chunk_idx >= chunks.size()) {
239 break;
240 }
241 const auto& chunk = chunks[chunk_idx];
242 for (size_t edge_idx = chunk.start; edge_idx < chunk.start + chunk.size; edge_idx += 2) {
243 round.extend_edges(extended_edges, polynomials, edge_idx);
244 accumulate_one_edge<Flavor>(
245 round, thread_accumulators[slot_idx], extended_edges, params, FF(7));
246 }
247 }
248 });
249 }
250
251 Tuple total{};
252 for (const auto& accum : thread_accumulators) {
254 }
255 benchmark::DoNotOptimize(total);
256 }
257
258 size_t active_edges = 0;
259 for (const auto& block : blocks) {
260 active_edges += block.size;
261 }
262 state.counters["active_edge_pairs"] = static_cast<double>(active_edges / 2);
263 state.counters["active_pct"] = 100.0 * static_cast<double>(active_edges) / static_cast<double>(round_size);
264 state.counters["blocks"] = static_cast<double>(blocks.size());
265 state.counters["relations"] = static_cast<double>(Flavor::NUM_RELATIONS);
266 state.counters["subrelations"] = static_cast<double>(Flavor::NUM_SUBRELATIONS);
267 state.counters["threads"] = static_cast<double>(get_num_cpus());
268}
269
270struct NanoSpec {
271 size_t relations = 0;
272 size_t subrelations = 0;
273 size_t heavy_period = 0;
274};
275
276template <Scheduler scheduler> void bench_nano_scheduler(benchmark::State& state)
277{
278 using FF = bb::fr;
279 const size_t log_n = static_cast<size_t>(state.range(0));
280 const size_t rows = size_t{ 1 } << log_n;
281 const bool imbalanced = static_cast<bool>(state.range(1));
282 const auto blocks = make_active_blocks(rows, /*fragmented=*/true);
283 const NanoSpec spec{ .relations = 12, .subrelations = 36, .heavy_period = imbalanced ? 8UL : 1UL };
284 constexpr size_t ROWS_PER_CHUNK = 64;
285
286 auto do_row = [&](std::array<FF, 64>& accum, const size_t row) {
287 const bool heavy = ((row / 2) % spec.heavy_period) == 0;
288 const size_t active_relations = heavy ? spec.relations : 2;
289 FF x = FF(static_cast<uint64_t>((row & 255) + 3));
290 for (size_t relation_idx = 0; relation_idx < active_relations; ++relation_idx) {
291 for (size_t subrelation_idx = 0; subrelation_idx < spec.subrelations / spec.relations; ++subrelation_idx) {
292 x = x * FF(static_cast<uint64_t>(relation_idx + 5)) + FF(static_cast<uint64_t>(subrelation_idx + 7));
293 accum[relation_idx * 4 + subrelation_idx] += x;
294 }
295 }
296 };
297
298 for (auto _ : state) {
300 for (auto& accum : accumulators) {
301 std::fill(accum.begin(), accum.end(), FF::zero());
302 }
303
304 if constexpr (scheduler == Scheduler::STATIC_BLOCKS) {
305 parallel_for([&](ThreadChunk chunk) {
306 for (const auto& block : blocks) {
307 const size_t iterations = block.size / 2;
308 for (size_t i : chunk.range(iterations)) {
309 do_row(accumulators[chunk.thread_index], block.start + i * 2);
310 }
311 }
312 });
313 } else {
315 for (const auto& block : blocks) {
316 for (size_t start = block.start; start < block.start + block.size; start += ROWS_PER_CHUNK) {
317 chunks.push_back(ActiveBlock{
318 .start = start,
319 .size = std::min(ROWS_PER_CHUNK, block.start + block.size - start),
320 });
321 }
322 }
323
324 std::atomic<size_t> next_chunk{ 0 };
325 const size_t num_slots = std::min(get_num_cpus(), std::max<size_t>(chunks.size(), 1));
326 accumulators.resize(num_slots);
327 parallel_for(num_slots, [&](size_t slot_idx) {
328 while (true) {
329 const size_t chunk_idx = next_chunk.fetch_add(1, std::memory_order_relaxed);
330 if (chunk_idx >= chunks.size()) {
331 break;
332 }
333 const auto& chunk = chunks[chunk_idx];
334 for (size_t edge_idx = chunk.start; edge_idx < chunk.start + chunk.size; edge_idx += 2) {
335 do_row(accumulators[slot_idx], edge_idx);
336 }
337 }
338 });
339 }
340 benchmark::DoNotOptimize(accumulators);
341 }
342
343 state.counters["relations"] = static_cast<double>(spec.relations);
344 state.counters["subrelations"] = static_cast<double>(spec.subrelations);
345 state.counters["threads"] = static_cast<double>(get_num_cpus());
346 state.counters["imbalanced"] = imbalanced ? 1.0 : 0.0;
347}
348
349template <typename Flavor> void bench_compute_univariate_round0(benchmark::State& state)
350{
351 using FF = typename Flavor::FF;
352 using Round = SumcheckProverRound<Flavor>;
353 const size_t log_n = static_cast<size_t>(state.range(0));
354 const size_t round_size = size_t{ 1 } << log_n;
355
356 SyntheticPolynomials<Flavor> synthetic_polynomials(round_size);
357 auto params = make_relation_parameters<Flavor>();
358 auto alphas = make_subrelation_separators<Flavor>();
359 GateSeparatorPolynomial<FF> gate_separators(make_challenges<FF>(log_n), log_n);
360
361 for (auto _ : state) {
362 Round round(round_size);
363 auto result = round.compute_univariate(synthetic_polynomials.polynomials, params, gate_separators, alphas);
364 benchmark::DoNotOptimize(result);
365 }
366
367 state.counters["relations"] = static_cast<double>(Flavor::NUM_RELATIONS);
368 state.counters["subrelations"] = static_cast<double>(Flavor::NUM_SUBRELATIONS);
369 state.counters["threads"] = static_cast<double>(get_num_cpus());
370}
371
372// log_n sizes swept by the size-dependent benches. Override with SUMCHECK_BENCH_LOG_N
373// (comma-separated), e.g. SUMCHECK_BENCH_LOG_N=16,18 to match a specific flow's circuit sizes.
374std::vector<int> log_n_sweep()
375{
376 const char* env = std::getenv("SUMCHECK_BENCH_LOG_N");
377 if (env == nullptr) {
378 return { 15, 17, 19 };
379 }
380 std::vector<int> sizes;
381 std::string spec(env);
382 size_t pos = 0;
383 while (pos < spec.size()) {
384 size_t comma = spec.find(',', pos);
385 const std::string token = spec.substr(pos, comma == std::string::npos ? std::string::npos : comma - pos);
386 if (!token.empty()) {
387 sizes.push_back(std::stoi(token));
388 }
389 if (comma == std::string::npos) {
390 break;
391 }
392 pos = comma + 1;
393 }
394 return sizes.empty() ? std::vector<int>{ 17 } : sizes;
395}
396
397template <typename Flavor>
398void register_flavor_benches(const std::string& name, const bool fragmented, const std::vector<int>& sizes)
399{
400 benchmark::RegisterBenchmark((name + "/accumulate_relations_only").c_str(),
401 &bench_accumulate_relations_only<Flavor>)
402 ->UseRealTime();
403 auto* loop_static = benchmark::RegisterBenchmark((name + "/loop_static_blocks").c_str(),
404 &bench_sumcheck_loop_shape<Flavor>,
405 Scheduler::STATIC_BLOCKS,
406 fragmented);
407 auto* loop_stealing = benchmark::RegisterBenchmark((name + "/loop_chunk_stealing").c_str(),
408 &bench_sumcheck_loop_shape<Flavor>,
409 Scheduler::CHUNK_STEALING,
410 fragmented);
411 auto* univariate_round0 = benchmark::RegisterBenchmark((name + "/compute_univariate_round0").c_str(),
412 &bench_compute_univariate_round0<Flavor>);
413 for (const int log_n : sizes) {
414 loop_static->Arg(log_n);
415 loop_stealing->Arg(log_n);
416 univariate_round0->Arg(log_n);
417 }
418 loop_static->UseRealTime()->Unit(benchmark::kMillisecond);
419 loop_stealing->UseRealTime()->Unit(benchmark::kMillisecond);
420 univariate_round0->UseRealTime()->Unit(benchmark::kMillisecond);
421}
422
423} // namespace
424
425int main(int argc, char** argv)
426{
427 const std::vector<int> sizes = log_n_sweep();
428 register_flavor_benches<bb::MegaZKFlavor>("MegaZK", /*fragmented=*/false, sizes);
429 register_flavor_benches<bb::TranslatorShortMonomialFlavor>("TranslatorShort", /*fragmented=*/true, sizes);
430 register_flavor_benches<bb::ECCVMShortMonomialFlavor>("ECCVMShort", /*fragmented=*/false, sizes);
431 benchmark::RegisterBenchmark("Nano/static_blocks", &bench_nano_scheduler<Scheduler::STATIC_BLOCKS>)
432 ->Args({ 17, 0 })
433 ->Args({ 17, 1 })
434 ->UseRealTime()
435 ->Unit(benchmark::kMillisecond);
436 benchmark::RegisterBenchmark("Nano/chunk_stealing", &bench_nano_scheduler<Scheduler::CHUNK_STEALING>)
437 ->Args({ 17, 0 })
438 ->Args({ 17, 1 })
439 ->UseRealTime()
440 ->Unit(benchmark::kMillisecond);
441
442 benchmark::Initialize(&argc, argv);
443 benchmark::RunSpecifiedBenchmarks();
444 benchmark::Shutdown();
445 return 0;
446}
A container for the prover polynomials.
typename Curve::ScalarField FF
static constexpr size_t NUM_SUBRELATIONS
static constexpr size_t NUM_ALL_ENTITIES
static constexpr size_t NUM_RELATIONS
static constexpr void add_nested_tuples(Tuple &tuple_1, const Tuple &tuple_2)
Componentwise addition of nested tuples (tuples of tuples)
Definition utils.hpp:118
Imlementation of the Sumcheck prover round.
decltype(create_sumcheck_tuple_of_tuples_of_univariates< Relations >()) SumcheckTupleOfTuplesOfUnivariates
SumcheckRoundUnivariate compute_univariate(ProverPolynomialsOrPartiallyEvaluatedMultivariates &polynomials, const bb::RelationParameters< FF > &relation_parameters, const bb::GateSeparatorPolynomial< FF > &gate_separators, const SubrelationSeparators &alphas)
Return the evaluations of the round univariate at .
static void extend_edges(ExtendedEdges &extended_edges, const ProverPolynomialsOrPartiallyEvaluatedMultivariates &multivariates, const size_t edge_idx)
To compute the round univariate in Round , the prover first computes the values of Honk polynomials ...
std::array< FF, Flavor::NUM_SUBRELATIONS - 1 > SubrelationSeparators
void accumulate_relation_univariates_public(SumcheckTupleOfTuplesOfUnivariates &univariate_accumulators, const auto &extended_edges, const bb::RelationParameters< FF > &relation_parameters, const FF &scaling_factor)
typename ECCVMFlavor::ProverPolynomials ProverPolynomials
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
size_t get_num_cpus()
Definition thread.cpp:34
field< Bn254FrParams > fr
Definition fr.hpp:155
void parallel_for(size_t num_iterations, const std::function< void(size_t)> &func)
Definition thread.cpp:112
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string name
Container for parameters used by the grand product (permutation, lookup) Honk relations.
size_t thread_index
Definition thread.hpp:150
VectorField result