Skip to content

Commit

Permalink
refactor: Remove VK computation Pg prover flow; improve benchmark to …
Browse files Browse the repository at this point in the history
…reflect possible optimization (#4639)

Closes AztecProtocol/barretenberg#833.


Properly separate prover and verifier information in the implemented
Protogalaxy protocol according to the paper. The prover now returns a
folded `ProverInstance` and the verifier a folded `VerifierInstance`.
Previously the structure of the `ProverInstance` contained also
information only relevant for the `VerifierInstance` (i.e. the
verification key). With this separation, the prover doesn't need to send
the previous verifier accumulator through the transcript anymore.
Furthermore, this PR refactors the ClientIVC to work with this change
(enabling precomputation of verification keys) as well as the recursive
folding and decider verifiers. The latter required creating a stdlib
counterpart of `VeriferInstance` since the previous verifier accumulator
(\phi*) is now received from the previous folding iteration.

The PR also implements an optimization that is possible in the
application client IVC scheme that was not previously reflected in our
benchmarks. Namely, it is possible for the IVC provers first fold to be
a function circuit-function circuit fold. This means that we can remove
one kernel iteration (including the construction of the kernel's
witnesses and the protgalaxy prover execution). Bechmarks follow. Note:
looking at just the difference in commitment times, we have observed
that the commitment cost was reduced by 4s.


# Benchmarks
## Before
```
Benchmarking lock created at ~/BENCHMARK_IN_PROGRESS.
ivc_bench                                                                                                                                                   100%   16MB  65.3MB/s   00:00    
2024-02-23T15:02:49+00:00
Running ./ivc_bench
Run on (16 X 3000 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x8)
  L1 Instruction 32 KiB (x8)
  L2 Unified 1024 KiB (x8)
  L3 Unified 36608 KiB (x1)
Load Average: 5.87, 2.74, 1.08
----------------------------------------------------------
Benchmark                Time             CPU   Iterations
----------------------------------------------------------
IvcBench/Full/6      39833 ms        37395 ms            1
Benchmarking lock deleted.
```
## After
```
ivc_bench                                                                                                                                                   100%   16MB  61.5MB/s   00:00    
2024-02-23T15:00:54+00:00
Running ./ivc_bench
Run on (16 X 3000 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x8)
  L1 Instruction 32 KiB (x8)
  L2 Unified 1024 KiB (x8)
  L3 Unified 36608 KiB (x1)
Load Average: 0.17, 0.36, 0.17
----------------------------------------------------------
Benchmark                Time             CPU   Iterations
----------------------------------------------------------
IvcBench/Full/6      32817 ms        31790 ms            1
```

---------

Co-authored-by: ludamad <domuradical@gmail.com>
Co-authored-by: codygunton <codygunton@gmail.com>
  • Loading branch information
3 people committed Feb 29, 2024
1 parent b0bc772 commit c1709b3
Show file tree
Hide file tree
Showing 43 changed files with 930 additions and 982 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ namespace {
class ClientIVCBench : public benchmark::Fixture {
public:
using Builder = GoblinUltraCircuitBuilder;
using VerifierFoldData = GoblinMockCircuits::VerifierFoldData;

// Number of function circuits to accumulate(based on Zacs target numbers)
static constexpr size_t NUM_ITERATIONS_MEDIUM_COMPLEXITY = 6;
Expand All @@ -43,28 +44,43 @@ class ClientIVCBench : public benchmark::Fixture {
{
const size_t size_hint = 1 << 17; // Size hint for reserving wires/selector vector memory in builders
// Initialize IVC with function circuit
Builder function_circuit{ size_hint, ivc.goblin.op_queue };
Builder initial_function_circuit{ size_hint, ivc.goblin.op_queue };
GoblinMockCircuits::construct_mock_function_circuit(initial_function_circuit);
ivc.initialize(initial_function_circuit);
auto kernel_verifier_accumulator = std::make_shared<ClientIVC::VerifierInstance>(ivc.vks.first_func_vk);

// Accumulate another function circuit
Builder function_circuit{ ivc.goblin.op_queue };
GoblinMockCircuits::construct_mock_function_circuit(function_circuit);
ivc.initialize(function_circuit);
auto function_fold_proof = ivc.accumulate(function_circuit);
VerifierFoldData function_fold_output = { function_fold_proof, ivc.vks.func_vk };

// Accumulate kernel circuit (first kernel mocked as simple circuit since no folding proofs yet)
// Create and accumulate the first folding kernel which only verifies the accumulation of a function circuit
Builder kernel_circuit{ size_hint, ivc.goblin.op_queue };
GoblinMockCircuits::construct_mock_function_circuit(kernel_circuit);
kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel(
kernel_circuit, function_fold_output, {}, kernel_verifier_accumulator);
auto kernel_fold_proof = ivc.accumulate(kernel_circuit);
VerifierFoldData kernel_fold_output = { kernel_fold_proof, ivc.vks.first_kernel_vk };

auto NUM_CIRCUITS = static_cast<size_t>(state.range(0));
NUM_CIRCUITS -= 1; // Subtract one to account for the "initialization" round above
// Subtract two to account for the "initialization" round above i.e. we have already folded two function
// circuits
NUM_CIRCUITS -= 2;
for (size_t circuit_idx = 0; circuit_idx < NUM_CIRCUITS; ++circuit_idx) {

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

// Accumulate kernel circuit
// Create kernel circuit containing the recursive folding verification of a function circuit and a kernel
// circuit and accumulate it
Builder kernel_circuit{ size_hint, 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);
kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel(
kernel_circuit, function_fold_output, kernel_fold_output, kernel_verifier_accumulator);

kernel_fold_proof = ivc.accumulate(kernel_circuit);
kernel_fold_output = { kernel_fold_proof, ivc.vks.kernel_vk };
}
}
};
Expand All @@ -76,7 +92,7 @@ class ClientIVCBench : public benchmark::Fixture {
BENCHMARK_DEFINE_F(ClientIVCBench, Full)(benchmark::State& state)
{
ClientIVC ivc;

ivc.precompute_folding_verification_keys();
for (auto _ : state) {
BB_REPORT_OP_COUNT_IN_BENCH(state);
// Perform a specified number of iterations of function/kernel accumulation
Expand All @@ -94,7 +110,7 @@ BENCHMARK_DEFINE_F(ClientIVCBench, Full)(benchmark::State& state)
BENCHMARK_DEFINE_F(ClientIVCBench, Accumulate)(benchmark::State& state)
{
ClientIVC ivc;

ivc.precompute_folding_verification_keys();
// Perform a specified number of iterations of function/kernel accumulation
for (auto _ : state) {
BB_REPORT_OP_COUNT_IN_BENCH(state);
Expand All @@ -109,7 +125,6 @@ BENCHMARK_DEFINE_F(ClientIVCBench, Accumulate)(benchmark::State& state)
BENCHMARK_DEFINE_F(ClientIVCBench, Decide)(benchmark::State& state)
{
ClientIVC ivc;

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

Expand Down Expand Up @@ -145,7 +160,7 @@ BENCHMARK_DEFINE_F(ClientIVCBench, ECCVM)(benchmark::State& state)
BENCHMARK_DEFINE_F(ClientIVCBench, Translator)(benchmark::State& state)
{
ClientIVC ivc;

ivc.precompute_folding_verification_keys();
BB_REPORT_OP_COUNT_IN_BENCH(state);
// Perform a specified number of iterations of function/kernel accumulation
perform_ivc_accumulation_rounds(state, ivc);
Expand All @@ -159,7 +174,6 @@ BENCHMARK_DEFINE_F(ClientIVCBench, Translator)(benchmark::State& state)

#define ARGS \
Arg(ClientIVCBench::NUM_ITERATIONS_MEDIUM_COMPLEXITY) \
->Arg(1 << 0) \
->Arg(1 << 1) \
->Arg(1 << 2) \
->Arg(1 << 3) \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ template <typename Composer> void fold_one(State& state) noexcept
static_assert(std::same_as<Flavor, UltraFlavor>);
bb::mock_proofs::generate_basic_arithmetic_circuit(builder, log2_num_gates);
}
return composer.create_instance(builder);
return composer.create_prover_instance(builder);
};

std::shared_ptr<Instance> instance_1 = construct_instance();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ void _bench_round(::benchmark::State& state,
void (*F)(ProtoGalaxyProver_<ProverInstances_<typename Composer::Flavor, 2>>&))
{
using Flavor = typename Composer::Flavor;
using Instance = ProverInstance_<Flavor>;
using Builder = typename Flavor::CircuitBuilder;

bb::srs::init_crs_factory("../srs_db/ignition");
Expand All @@ -28,16 +27,16 @@ void _bench_round(::benchmark::State& state,
static_assert(std::same_as<Flavor, UltraFlavor>);
bb::mock_proofs::generate_basic_arithmetic_circuit(builder, log2_num_gates);
}
return composer.create_instance(builder);
return composer.create_prover_instance(builder);
};

std::shared_ptr<Instance> instance_1 = construct_instance();
std::shared_ptr<Instance> instance_2 = construct_instance();
auto prover_instance_1 = construct_instance();
auto prover_instance_2 = construct_instance();

auto folding_prover = composer.create_folding_prover({ instance_1, instance_2 });
auto folding_prover = composer.create_folding_prover({ prover_instance_1, prover_instance_2 });

// prepare the prover state
folding_prover.state.accumulator = instance_1;
folding_prover.state.accumulator = prover_instance_1;
folding_prover.state.deltas.resize(log2_num_gates);
std::fill_n(folding_prover.state.deltas.begin(), log2_num_gates, 0);
folding_prover.state.perturbator = Flavor::Polynomial::random(1 << log2_num_gates);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ inline UltraProver get_prover(UltraComposer& composer,
{
UltraComposer::CircuitBuilder builder;
test_circuit_function(builder, num_iterations);
std::shared_ptr<UltraComposer::Instance> instance = composer.create_instance(builder);
std::shared_ptr<UltraComposer::ProverInstance> instance = composer.create_prover_instance(builder);
return composer.create_prover(instance);
}

Expand All @@ -64,7 +64,7 @@ inline GoblinUltraProver get_prover(GoblinUltraComposer& composer,
{
GoblinUltraComposer::CircuitBuilder builder;
test_circuit_function(builder, num_iterations);
std::shared_ptr<GoblinUltraComposer::Instance> instance = composer.create_instance(builder);
std::shared_ptr<GoblinUltraComposer::ProverInstance> instance = composer.create_prover_instance(builder);
return composer.create_prover(instance);
}

Expand Down
80 changes: 66 additions & 14 deletions barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ void ClientIVC::initialize(ClientCircuit& circuit)
{
goblin.merge(circuit); // Construct new merge proof
Composer composer;
fold_output.accumulator = composer.create_instance(circuit);
prover_fold_output.accumulator = composer.create_prover_instance(circuit);
}

/**
Expand All @@ -32,11 +32,10 @@ ClientIVC::FoldProof ClientIVC::accumulate(ClientCircuit& circuit)
{
goblin.merge(circuit); // Add recursive merge verifier and construct new merge proof
Composer composer;
auto instance = composer.create_instance(circuit);
std::vector<std::shared_ptr<Instance>> instances{ fold_output.accumulator, instance };
auto folding_prover = composer.create_folding_prover(instances);
fold_output = folding_prover.fold_instances();
return fold_output.folding_data;
prover_instance = composer.create_prover_instance(circuit);
auto folding_prover = composer.create_folding_prover({ prover_fold_output.accumulator, prover_instance });
prover_fold_output = folding_prover.fold_instances();
return prover_fold_output.folding_data;
}

/**
Expand All @@ -46,7 +45,7 @@ ClientIVC::FoldProof ClientIVC::accumulate(ClientCircuit& circuit)
*/
ClientIVC::Proof ClientIVC::prove()
{
return { fold_output.folding_data, decider_prove(), goblin.prove() };
return { prover_fold_output.folding_data, decider_prove(), goblin.prove() };
}

/**
Expand All @@ -55,19 +54,19 @@ ClientIVC::Proof ClientIVC::prove()
* @param proof
* @return bool
*/
bool ClientIVC::verify(Proof& proof)
bool ClientIVC::verify(Proof& proof, const std::vector<VerifierAccumulator>& verifier_instances)
{
// Goblin verification (merge, eccvm, translator)
bool goblin_verified = goblin.verify(proof.goblin_proof);

// Decider verification
Composer composer;
auto folding_verifier = composer.create_folding_verifier();
bool folding_verified = folding_verifier.verify_folding_proof(proof.fold_proof);
// NOTE: Use of member accumulator here will go away with removal of vkey from ProverInstance
auto decider_verifier = composer.create_decider_verifier(fold_output.accumulator);
auto folding_verifier = composer.create_folding_verifier({ verifier_instances[0], verifier_instances[1] });
auto verifier_accumulator = folding_verifier.verify_folding_proof(proof.fold_proof);

auto decider_verifier = composer.create_decider_verifier(verifier_accumulator);
bool decision = decider_verifier.verify_proof(proof.decider_proof);
return goblin_verified && folding_verified && decision;
return goblin_verified && decision;
}

/**
Expand All @@ -78,8 +77,61 @@ bool ClientIVC::verify(Proof& proof)
HonkProof ClientIVC::decider_prove() const
{
Composer composer;
auto decider_prover = composer.create_decider_prover(fold_output.accumulator);
auto decider_prover = composer.create_decider_prover(prover_fold_output.accumulator);
return decider_prover.construct_proof();
}

/**
* @brief Precompute the array of verification keys by simulating folding. There will be 4 different verification keys:
* initial function verification key (without recursive merge verifier), subsequent function verification key (with
* recursive merge verifier), initial kernel verification key (with recursive merge verifier appended, no previous
* kernel to fold), "full" kernel verification key( two recursive folding verifiers and merge verifier).
*
*/
void ClientIVC::precompute_folding_verification_keys()
{
using VerifierInstance = VerifierInstance_<GoblinUltraFlavor>;

ClientCircuit initial_function_circuit{ goblin.op_queue };
GoblinMockCircuits::construct_mock_function_circuit(initial_function_circuit);

// Initialise both the first prover and verifier accumulator from the inital function circuit
initialize(initial_function_circuit);
vks.first_func_vk = prover_fold_output.accumulator->verification_key;
auto initial_verifier_acc = std::make_shared<VerifierInstance>(vks.first_func_vk);

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

// Create its verification key (we have called accumulate so it includes the recursive merge verifier)
vks.func_vk = prover_instance->verification_key;

// Create the initial kernel iteration and precompute its verification key
ClientCircuit kernel_circuit{ goblin.op_queue };
auto kernel_acc = GoblinMockCircuits::construct_mock_folding_kernel(
kernel_circuit, { function_fold_proof, vks.func_vk }, {}, initial_verifier_acc);
auto kernel_fold_proof = accumulate(kernel_circuit);
vks.first_kernel_vk = prover_instance->verification_key;

// Create another mock function circuit to run the full kernel
function_circuit = ClientCircuit{ goblin.op_queue };
GoblinMockCircuits::construct_mock_function_circuit(function_circuit);
function_fold_proof = accumulate(function_circuit);

// Create the full kernel circuit and compute verification key
kernel_circuit = GoblinUltraCircuitBuilder{ goblin.op_queue };
kernel_acc = GoblinMockCircuits::construct_mock_folding_kernel(
kernel_circuit, { function_fold_proof, vks.func_vk }, { kernel_fold_proof, vks.first_kernel_vk }, kernel_acc);
kernel_fold_proof = accumulate(kernel_circuit);

vks.kernel_vk = prover_instance->verification_key;

// Clean the ivc state
goblin.op_queue = std::make_shared<Goblin::OpQueue>();
goblin.merge_proof_exists = false;
GoblinMockCircuits::perform_op_queue_interactions_for_mock_first_circuit(goblin.op_queue);
}

} // namespace bb
31 changes: 26 additions & 5 deletions barretenberg/cpp/src/barretenberg/client_ivc/client_ivc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@ class ClientIVC {

public:
using Flavor = GoblinUltraFlavor;
using VerificationKey = Flavor::VerificationKey;
using FF = Flavor::FF;
using FoldProof = std::vector<FF>;
using Accumulator = std::shared_ptr<ProverInstance_<Flavor>>;
using ProverAccumulator = std::shared_ptr<ProverInstance_<Flavor>>;
using VerifierAccumulator = std::shared_ptr<VerifierInstance_<Flavor>>;
using ProverInstance = ProverInstance_<GoblinUltraFlavor>;
using VerifierInstance = VerifierInstance_<GoblinUltraFlavor>;
using ClientCircuit = GoblinUltraCircuitBuilder; // can only be GoblinUltra

// A full proof for the IVC scheme
Expand All @@ -28,14 +32,27 @@ class ClientIVC {
Goblin::Proof goblin_proof;
};

struct PrecomputedVerificationKeys {
std::shared_ptr<VerificationKey> first_func_vk;
std::shared_ptr<VerificationKey> func_vk;
std::shared_ptr<VerificationKey> first_kernel_vk;
std::shared_ptr<VerificationKey> kernel_vk;
};

private:
using FoldingOutput = FoldingResult<Flavor>;
using Instance = ProverInstance_<GoblinUltraFlavor>;
using ProverFoldOutput = FoldingResult<GoblinUltraFlavor>;
using Composer = GoblinUltraComposer;
// Note: We need to save the last instance that was folded in order to compute its verification key, this will not
// be needed in the real IVC as they are provided as inputs

public:
Goblin goblin;
FoldingOutput fold_output;
ProverFoldOutput prover_fold_output;
ProverAccumulator prover_accumulator;
PrecomputedVerificationKeys vks;
// Note: We need to save the last instance that was folded in order to compute its verification key, this will not
// be needed in the real IVC as they are provided as inputs
std::shared_ptr<ProverInstance> prover_instance;

ClientIVC();

Expand All @@ -45,8 +62,12 @@ class ClientIVC {

Proof prove();

bool verify(Proof& proof);
bool verify(Proof& proof, const std::vector<VerifierAccumulator>& verifier_instances);

HonkProof decider_prove() const;

void decider_prove_and_verify(const VerifierAccumulator&) const;

void precompute_folding_verification_keys();
};
} // namespace bb
Loading

0 comments on commit c1709b3

Please sign in to comment.