Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Parallelise kernel and function circuit construction in client IVC #4841

Merged
merged 4 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 };
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need the prover to be dependent on the current circuit builder instead of taking information from the global op_queue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was this a bug?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really. Since we only did circuits sequentially, we didn't care about this, since both pointers pointed to the same object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I see

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
Loading