diff --git a/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp index 9840e596f89..41ff9f8ae75 100644 --- a/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp +++ b/barretenberg/cpp/src/barretenberg/benchmark/client_ivc_bench/client_ivc.bench.cpp @@ -43,43 +43,100 @@ class ClientIVCBench : public benchmark::Fixture { static void perform_ivc_accumulation_rounds(State& state, ClientIVC& ivc) { const size_t size_hint = 1 << 17; // Size hint for reserving wires/selector vector memory in builders - // Initialize IVC with function circuit - 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(ivc.vks.first_func_vk); - + std::vector initial_function_circuits(2); + + // Construct 2 starting function circuits in parallel + parallel_for(2, [&](size_t circuit_index) { + GoblinMockCircuits::construct_mock_function_circuit(initial_function_circuits[circuit_index]); + }); + + // Prepend queue to the first circuit + initial_function_circuits[0].op_queue->prepend_previous_queue(*ivc.goblin.op_queue); + // Initialize ivc + ivc.initialize(initial_function_circuits[0]); + // Retrieve the queue + std::swap(*ivc.goblin.op_queue, *initial_function_circuits[0].op_queue); + + // Prepend queue to the second circuit + initial_function_circuits[1].op_queue->prepend_previous_queue(*ivc.goblin.op_queue); // Accumulate another function circuit - Builder function_circuit{ ivc.goblin.op_queue }; - GoblinMockCircuits::construct_mock_function_circuit(function_circuit); - auto function_fold_proof = ivc.accumulate(function_circuit); + auto function_fold_proof = ivc.accumulate(initial_function_circuits[1]); + // Retrieve the queue + std::swap(*ivc.goblin.op_queue, *initial_function_circuits[1].op_queue); VerifierFoldData function_fold_output = { function_fold_proof, ivc.vks.func_vk }; - // 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 }; - 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 }; + // Free memory + initial_function_circuits.clear(); auto NUM_CIRCUITS = static_cast(state.range(0)); // Subtract two to account for the "initialization" round above i.e. we have already folded two function // circuits NUM_CIRCUITS -= 2; + + // The accumulator for kernel uses the function accumulation verification key + auto kernel_verifier_accumulator = std::make_shared(ivc.vks.first_func_vk); + + VerifierFoldData kernel_fold_output; for (size_t circuit_idx = 0; circuit_idx < NUM_CIRCUITS; ++circuit_idx) { + Builder kernel_circuit{ size_hint, ivc.goblin.op_queue }; + Builder function_circuit{ size_hint }; + // Construct function and kernel circuits in parallel + parallel_for(2, [&](size_t workload_idx) { + // workload index is 0 for kernel and 1 for function + if (workload_idx == 0) { + if (circuit_idx == 0) { + + // Create the first folding kernel which only verifies the accumulation of a + // function circuit + kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel( + kernel_circuit, function_fold_output, {}, kernel_verifier_accumulator); + } else { + // Create kernel circuit containing the recursive folding verification of a function circuit and + // a kernel circuit + kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel( + kernel_circuit, function_fold_output, kernel_fold_output, kernel_verifier_accumulator); + } + } else { + GoblinMockCircuits::construct_mock_function_circuit(function_circuit); + } + }); + + // No need to prepend queue, it's the same after last swap + // Accumulate kernel circuit + auto kernel_fold_proof = ivc.accumulate(kernel_circuit); + + // First iteration and the following ones differ + if (circuit_idx == 0) { + kernel_fold_output = { kernel_fold_proof, ivc.vks.first_kernel_vk }; + } else { + kernel_fold_output = { kernel_fold_proof, ivc.vks.kernel_vk }; + } + + // Prepend queue to function circuit + function_circuit.op_queue->prepend_previous_queue(*ivc.goblin.op_queue); + // 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 }; - // Create kernel circuit containing the recursive folding verification of a function circuit and a kernel - // circuit and accumulate it + // Retrieve queue + std::swap(*ivc.goblin.op_queue, *function_circuit.op_queue); + } + // If we haven't entered the cycle, the kernel proof accumulates just function proofs + if (NUM_CIRCUITS == 0) { + // 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 }; + auto kernel_verifier_accumulator = std::make_shared(ivc.vks.first_func_vk); + kernel_verifier_accumulator = GoblinMockCircuits::construct_mock_folding_kernel( + kernel_circuit, function_fold_output, {}, kernel_verifier_accumulator); + auto kernel_fold_proof = ivc.accumulate(kernel_circuit); + kernel_fold_output = { kernel_fold_proof, ivc.vks.first_kernel_vk }; + } else { Builder kernel_circuit{ size_hint, ivc.goblin.op_queue }; 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); + auto kernel_fold_proof = ivc.accumulate(kernel_circuit); kernel_fold_output = { kernel_fold_proof, ivc.vks.kernel_vk }; } } diff --git a/barretenberg/cpp/src/barretenberg/goblin/goblin.hpp b/barretenberg/cpp/src/barretenberg/goblin/goblin.hpp index bb5772f7d31..3b055844b2f 100644 --- a/barretenberg/cpp/src/barretenberg/goblin/goblin.hpp +++ b/barretenberg/cpp/src/barretenberg/goblin/goblin.hpp @@ -109,7 +109,7 @@ class Goblin { auto ultra_proof = prover.construct_proof(); // Construct and store the merge proof to be recursively verified on the next call to accumulate - MergeProver merge_prover{ op_queue }; + MergeProver merge_prover{ circuit_builder.op_queue }; merge_proof = merge_prover.construct_proof(); if (!merge_proof_exists) { @@ -137,7 +137,7 @@ class Goblin { } // Construct and store the merge proof to be recursively verified on the next call to accumulate - MergeProver merge_prover{ op_queue }; + MergeProver merge_prover{ circuit_builder.op_queue }; merge_proof = merge_prover.construct_proof(); if (!merge_proof_exists) { diff --git a/barretenberg/cpp/src/barretenberg/proof_system/op_queue/ecc_op_queue.hpp b/barretenberg/cpp/src/barretenberg/proof_system/op_queue/ecc_op_queue.hpp index c264cf8e1ea..3b1a6866967 100644 --- a/barretenberg/cpp/src/barretenberg/proof_system/op_queue/ecc_op_queue.hpp +++ b/barretenberg/cpp/src/barretenberg/proof_system/op_queue/ecc_op_queue.hpp @@ -77,6 +77,13 @@ class ECCOpQueue { ultra_ops_commitments = previous.ultra_ops_commitments; previous_ultra_ops_commitments = previous.previous_ultra_ops_commitments; } + /** + * @brief Prepend the information from the previous queue (used before accumulation/merge proof to be able to run + * circuit construction separately) + * + * @param previous_ptr + */ + void prepend_previous_queue(const ECCOpQueue* previous_ptr) { prepend_previous_queue(*previous_ptr); } /** * @brief Enable using std::swap on queues