Skip to content

Commit

Permalink
feat: IVC bench (AztecProtocol#4515)
Browse files Browse the repository at this point in the history
Adds benchmark suite for the PG-Goblin client IVC scheme. The only real
caveat is that we only consider function circuits of size 2^17 since our
PG code doesn't support folding unequal sizes.

Results at present:
```
-----------------------------------------------------------------
Benchmark                       Time             CPU   Iterations
-----------------------------------------------------------------
IvcBench/Full/6             66891 ms        63569 ms            1
```

---------
  • Loading branch information
ledwards2225 committed Feb 8, 2024
1 parent 1dd0ebf commit d8ae42b
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 21 deletions.
11 changes: 6 additions & 5 deletions barretenberg/cpp/src/barretenberg/benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
add_subdirectory(basics_bench)
add_subdirectory(decrypt_bench)
add_subdirectory(goblin_bench)
add_subdirectory(ipa_bench)
add_subdirectory(ivc_bench)
add_subdirectory(pippenger_bench)
add_subdirectory(plonk_bench)
add_subdirectory(ultra_bench)
add_subdirectory(goblin_bench)
add_subdirectory(basics_bench)
add_subdirectory(protogalaxy_bench)
add_subdirectory(relations_bench)
add_subdirectory(widgets_bench)
add_subdirectory(protogalaxy_bench)
add_subdirectory(ultra_bench)
add_subdirectory(widgets_bench)
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class GoblinBench : public benchmark::Fixture {
// Construct and accumulate the mock kernel circuit
// Note: in first round, kernel_accum is empty since there is no previous kernel to recursively verify
GoblinUltraCircuitBuilder circuit_builder{ goblin.op_queue };
GoblinMockCircuits::construct_mock_kernel_circuit(circuit_builder, function_accum, kernel_accum);
GoblinMockCircuits::construct_mock_recursion_kernel_circuit(circuit_builder, function_accum, kernel_accum);
kernel_accum = goblin.accumulate(circuit_builder);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
barretenberg_module(ivc_bench client_ivc stdlib_recursion stdlib_sha256 stdlib_merkle_tree stdlib_primitives)
178 changes: 178 additions & 0 deletions barretenberg/cpp/src/barretenberg/benchmark/ivc_bench/ivc.bench.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@

#include <benchmark/benchmark.h>

#include "barretenberg/benchmark/ultra_bench/mock_proofs.hpp"
#include "barretenberg/client_ivc/client_ivc.hpp"
#include "barretenberg/common/op_count_google_bench.hpp"
#include "barretenberg/goblin/goblin.hpp"
#include "barretenberg/goblin/mock_circuits.hpp"
#include "barretenberg/proof_system/circuit_builder/ultra_circuit_builder.hpp"
#include "barretenberg/ultra_honk/ultra_composer.hpp"

using namespace benchmark;
using namespace bb;

namespace {

/**
* @brief Benchmark suite for the aztec client PG-Goblin IVC scheme
*
*/
class IvcBench : public benchmark::Fixture {
public:
using Builder = GoblinUltraCircuitBuilder;

// Number of function circuits to accumulate(based on Zacs target numbers)
static constexpr size_t NUM_ITERATIONS_MEDIUM_COMPLEXITY = 6;

void SetUp([[maybe_unused]] const ::benchmark::State& state) override
{
bb::srs::init_crs_factory("../srs_db/ignition");
bb::srs::init_grumpkin_crs_factory("../srs_db/grumpkin");
}

/**
* @brief Perform a specified number of function circuit accumulation rounds
* @details Each round "accumulates" a mock function circuit and a mock kernel circuit. Each round thus consists of
* the generation of two circuits, two folding proofs and two Merge proofs. To match the sizes called out in the
* spec
* (https://github.com/AztecProtocol/aztec-packages/blob/master/yellow-paper/docs/cryptography/performance-targets.md)
* we set the size of the function circuit to be 2^17. The first one should be 2^19 but we can't currently support
* folding circuits of unequal size.
*
*/
static void perform_ivc_accumulation_rounds(State& state, ClientIVC& ivc)
{
// Initialize IVC with function circuit
Builder function_circuit{ ivc.goblin.op_queue };
GoblinMockCircuits::construct_mock_function_circuit(function_circuit);
ivc.initialize(function_circuit);

// Accumulate kernel circuit (first kernel mocked as simple circuit since no folding proofs yet)
Builder kernel_circuit{ ivc.goblin.op_queue };
GoblinMockCircuits::construct_mock_function_circuit(kernel_circuit);
auto kernel_fold_proof = ivc.accumulate(kernel_circuit);

auto NUM_CIRCUITS = static_cast<size_t>(state.range(0));
NUM_CIRCUITS -= 1; // Subtract one to account for the "initialization" round above
for (size_t circuit_idx = 0; circuit_idx < NUM_CIRCUITS; ++circuit_idx) {

// Accumulate function circuit
Builder function_circuit{ ivc.goblin.op_queue };
GoblinMockCircuits::construct_mock_function_circuit(function_circuit);
auto function_fold_proof = ivc.accumulate(function_circuit);

// Accumulate kernel circuit
Builder kernel_circuit{ ivc.goblin.op_queue };
GoblinMockCircuits::construct_mock_folding_kernel(kernel_circuit, function_fold_proof, kernel_fold_proof);
auto kernel_fold_proof = ivc.accumulate(kernel_circuit);
}
}
};

/**
* @brief Benchmark the prover work for the full PG-Goblin IVC protocol
*
*/
BENCHMARK_DEFINE_F(IvcBench, Full)(benchmark::State& state)
{
ClientIVC ivc;

for (auto _ : state) {
BB_REPORT_OP_COUNT_IN_BENCH(state);
// Perform a specified number of iterations of function/kernel accumulation
perform_ivc_accumulation_rounds(state, ivc);

// Construct IVC scheme proof (fold, decider, merge, eccvm, translator)
ivc.prove();
}
}

/**
* @brief Benchmark only the accumulation rounds
*
*/
BENCHMARK_DEFINE_F(IvcBench, Accumulate)(benchmark::State& state)
{
ClientIVC ivc;

// Perform a specified number of iterations of function/kernel accumulation
for (auto _ : state) {
perform_ivc_accumulation_rounds(state, ivc);
}
}

/**
* @brief Benchmark only the Decider component
*
*/
BENCHMARK_DEFINE_F(IvcBench, Decide)(benchmark::State& state)
{
ClientIVC ivc;

BB_REPORT_OP_COUNT_IN_BENCH(state);
// Perform a specified number of iterations of function/kernel accumulation
perform_ivc_accumulation_rounds(state, ivc);

// Construct eccvm proof, measure only translator proof construction
for (auto _ : state) {
ivc.decider_prove();
}
}

/**
* @brief Benchmark only the ECCVM component
*
*/
BENCHMARK_DEFINE_F(IvcBench, ECCVM)(benchmark::State& state)
{
ClientIVC ivc;

BB_REPORT_OP_COUNT_IN_BENCH(state);
// Perform a specified number of iterations of function/kernel accumulation
perform_ivc_accumulation_rounds(state, ivc);

// Construct and measure eccvm only
for (auto _ : state) {
ivc.goblin.prove_eccvm();
}
}

/**
* @brief Benchmark only the Translator component
*
*/
BENCHMARK_DEFINE_F(IvcBench, Translator)(benchmark::State& state)
{
ClientIVC ivc;

BB_REPORT_OP_COUNT_IN_BENCH(state);
// Perform a specified number of iterations of function/kernel accumulation
perform_ivc_accumulation_rounds(state, ivc);

// Construct eccvm proof, measure only translator proof construction
ivc.goblin.prove_eccvm();
for (auto _ : state) {
ivc.goblin.prove_translator();
}
}

#define ARGS \
Arg(IvcBench::NUM_ITERATIONS_MEDIUM_COMPLEXITY) \
->Arg(1 << 0) \
->Arg(1 << 1) \
->Arg(1 << 2) \
->Arg(1 << 3) \
->Arg(1 << 4) \
->Arg(1 << 5) \
->Arg(1 << 6)

BENCHMARK_REGISTER_F(IvcBench, Full)->Unit(benchmark::kMillisecond)->ARGS;
BENCHMARK_REGISTER_F(IvcBench, Accumulate)->Unit(benchmark::kMillisecond)->ARGS;
BENCHMARK_REGISTER_F(IvcBench, Decide)->Unit(benchmark::kMillisecond)->ARGS;
BENCHMARK_REGISTER_F(IvcBench, ECCVM)->Unit(benchmark::kMillisecond)->ARGS;
BENCHMARK_REGISTER_F(IvcBench, Translator)->Unit(benchmark::kMillisecond)->ARGS;

} // namespace

BENCHMARK_MAIN();
22 changes: 14 additions & 8 deletions barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,7 @@ ClientIVC::FoldProof ClientIVC::accumulate(ClientCircuit& circuit)
*/
ClientIVC::Proof ClientIVC::prove()
{
// Construct Goblin proof (merge, eccvm, translator)
auto goblin_proof = goblin.prove();

// Construct decider proof for the final accumulator
Composer composer;
auto decider_prover = composer.create_decider_prover(fold_output.accumulator);
auto decider_proof = decider_prover.construct_proof();
return { goblin_proof, fold_output.folding_data, decider_proof };
return { fold_output.folding_data, decider_prove(), goblin.prove() };
}

/**
Expand All @@ -76,4 +69,17 @@ bool ClientIVC::verify(Proof& proof)
bool decision = decider_verifier.verify_proof(proof.decider_proof);
return goblin_verified && folding_verified && decision;
}

/**
* @brief Internal method for constructing a decider proof
*
* @return HonkProof
*/
HonkProof ClientIVC::decider_prove() const
{
Composer composer;
auto decider_prover = composer.create_decider_prover(fold_output.accumulator);
return decider_prover.construct_proof();
}

} // namespace bb
4 changes: 3 additions & 1 deletion barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ class ClientIVC {

// A full proof for the IVC scheme
struct Proof {
Goblin::Proof goblin_proof;
FoldProof fold_proof; // final fold proof
HonkProof decider_proof;
Goblin::Proof goblin_proof;
};

private:
Expand All @@ -46,5 +46,7 @@ class ClientIVC {
Proof prove();

bool verify(Proof& proof);

HonkProof decider_prove() const;
};
} // namespace bb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include "barretenberg/client_ivc/client_ivc.hpp"
#include "barretenberg/goblin/mock_circuits.hpp"
#include "barretenberg/proof_system/circuit_builder/goblin_ultra_circuit_builder.hpp"
#include "barretenberg/ultra_honk/ultra_composer.hpp"
#include <gtest/gtest.h>

using namespace bb;

/**
* @brief For benchmarking, we want to be sure that our mocking functions create circuits of a known size. We control
* this, to the degree that matters for proof construction time, using these "pinning tests" that fix values.
*
*/
class MockKernelTest : public ::testing::Test {
protected:
static void SetUpTestSuite() { srs::init_crs_factory("../srs_db/ignition"); }
};

TEST_F(MockKernelTest, PinFoldingKernelSizes)
{
ClientIVC ivc;

// Accumulate three circuits to generate two folding proofs for input to folding kernel
GoblinUltraCircuitBuilder circuit_1{ ivc.goblin.op_queue };
GoblinMockCircuits::construct_mock_function_circuit(circuit_1);
ivc.initialize(circuit_1);

GoblinUltraCircuitBuilder circuit_2{ ivc.goblin.op_queue };
GoblinMockCircuits::construct_mock_function_circuit(circuit_2);
auto fold_proof_1 = ivc.accumulate(circuit_2);

GoblinUltraCircuitBuilder circuit_3{ ivc.goblin.op_queue };
GoblinMockCircuits::construct_mock_function_circuit(circuit_3);
auto fold_proof_2 = ivc.accumulate(circuit_3);

// Construct kernel circuit
GoblinUltraCircuitBuilder kernel_circuit{ ivc.goblin.op_queue };
GoblinMockCircuits::construct_mock_folding_kernel(kernel_circuit, fold_proof_1, fold_proof_2);
GoblinUltraComposer composer;
auto instance = composer.create_instance(kernel_circuit);
EXPECT_EQ(instance->proving_key->log_circuit_size, 17);
}
45 changes: 41 additions & 4 deletions barretenberg/cpp/src/barretenberg/goblin/mock_circuits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include "barretenberg/stdlib/merkle_tree/merkle_tree.hpp"
#include "barretenberg/stdlib/primitives/curves/secp256k1.hpp"
#include "barretenberg/stdlib/primitives/packed_byte_array/packed_byte_array.hpp"
#include "barretenberg/stdlib/recursion/honk/verifier/protogalaxy_recursive_verifier.hpp"
#include "barretenberg/stdlib/recursion/honk/verifier/ultra_recursive_verifier.hpp"

namespace bb {
Expand Down Expand Up @@ -164,7 +165,7 @@ class GoblinMockCircuits {
}

/**
* @brief Construct a size 2^17 mock kernel circuit for benchmarking
* @brief Construct a size 2^17 mock kernel circuit based on vanilla recursion for benchmarking
* @details This circuit contains (1) some arbitrary operations representing general kernel logic, (2) recursive
* verification of a function circuit proof, and optionally (3) recursive verification of a previous kernel circuit
* proof. The arbitrary kernel logic is structured to bring the final dyadic circuit size of the kernel to 2^17.
Expand All @@ -174,9 +175,9 @@ class GoblinMockCircuits {
* @param function_accum {proof, vkey} for function circuit to be recursively verified
* @param prev_kernel_accum {proof, vkey} for previous kernel circuit to be recursively verified
*/
static void construct_mock_kernel_circuit(GoblinUltraBuilder& builder,
const KernelInput& function_accum,
const KernelInput& prev_kernel_accum)
static void construct_mock_recursion_kernel_circuit(GoblinUltraBuilder& builder,
const KernelInput& function_accum,
const KernelInput& prev_kernel_accum)
{
// Add operations representing general kernel logic e.g. state updates. Note: these are structured to make the
// kernel "full" within the dyadic size 2^17 (130914 gates)
Expand All @@ -198,6 +199,42 @@ class GoblinMockCircuits {
}
}

/**
* @brief Construct a mock kernel circuit based on folding
* @details This circuit contains (1) some arbitrary operations representing general kernel logic, (2) recursive
* folding verification of a function circuit folding proof, and (3) recursive folding verification of a previous
* kernel circuit folding proof. The arbitrary kernel logic is structured to bring the final dyadic circuit size of
* the kernel to 2^17.
*
* @param builder
* @param function_fold_proof
* @param kernel_fold_proof
*/
static void construct_mock_folding_kernel(GoblinUltraBuilder& builder,
const std::vector<FF>& function_fold_proof,
const std::vector<FF>& kernel_fold_proof)
{
using GURecursiveFlavor = GoblinUltraRecursiveFlavor_<GoblinUltraBuilder>;
using RecursiveVerifierInstances = ::bb::VerifierInstances_<GURecursiveFlavor, 2>;
using FoldingRecursiveVerifier =
bb::stdlib::recursion::honk::ProtoGalaxyRecursiveVerifier_<RecursiveVerifierInstances>;

// Add operations representing general kernel logic e.g. state updates. Note: these are structured to make the
// kernel "full" within the dyadic size 2^17 (130914 gates)
const size_t NUM_MERKLE_CHECKS = 20;
const size_t NUM_ECDSA_VERIFICATIONS = 1;
const size_t NUM_SHA_HASHES = 1;
stdlib::generate_merkle_membership_test_circuit(builder, NUM_MERKLE_CHECKS);
stdlib::generate_ecdsa_verification_test_circuit(builder, NUM_ECDSA_VERIFICATIONS);
stdlib::generate_sha256_test_circuit(builder, NUM_SHA_HASHES);

FoldingRecursiveVerifier verifier_1{ &builder };
verifier_1.verify_folding_proof(function_fold_proof);

FoldingRecursiveVerifier verifier_2{ &builder };
verifier_2.verify_folding_proof(kernel_fold_proof);
}

/**
* @brief A minimal version of the mock kernel (recursive verifiers only) for faster testing
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ TEST_F(MockCircuits, PinFunctionSizes)
run_test(false);
}

TEST_F(MockCircuits, PinKernelSizes)
TEST_F(MockCircuits, PinRecursionKernelSizes)
{
const auto run_test = [](bool large) {
{
Expand All @@ -45,7 +45,7 @@ TEST_F(MockCircuits, PinKernelSizes)
GoblinMockCircuits::construct_mock_function_circuit(app_circuit, large);
auto function_accum = goblin.accumulate(app_circuit);
GoblinUltraCircuitBuilder kernel_circuit{ goblin.op_queue };
GoblinMockCircuits::construct_mock_kernel_circuit(kernel_circuit, function_accum, kernel_accum);
GoblinMockCircuits::construct_mock_recursion_kernel_circuit(kernel_circuit, function_accum, kernel_accum);
GoblinUltraComposer composer;
auto instance = composer.create_instance(kernel_circuit);
if (large) {
Expand Down

0 comments on commit d8ae42b

Please sign in to comment.