Skip to content

Commit

Permalink
feat: Parallelise kernel and function circuit construction in client …
Browse files Browse the repository at this point in the history
…IVC (#4841)

Removes 2.9 seconds from 6 steps in client IVC by parallelising kernel
and function circuit construction (we construct the next function
circuit while we construct the kernel for the previous one).
Now:

![image](https://github.com/AztecProtocol/aztec-packages/assets/4798775/5534752a-da00-4533-b482-3fb848808498)

Was:

![image](https://github.com/AztecProtocol/aztec-packages/assets/4798775/9cb4ccfe-121d-4c91-9d95-c74c051bfcd9)
  • Loading branch information
Rumata888 authored Feb 29, 2024
1 parent 747fc33 commit 9c689d8
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<ClientIVC::VerifierInstance>(ivc.vks.first_func_vk);

std::vector<Builder> 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<size_t>(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<ClientIVC::VerifierInstance>(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<ClientIVC::VerifierInstance>(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 };
}
}
Expand Down
4 changes: 2 additions & 2 deletions barretenberg/cpp/src/barretenberg/goblin/goblin.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9c689d8

Please sign in to comment.