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

fix: memory fragmentation fixes to cut UltraHonk memory usage by 26% #11895

Merged
merged 17 commits into from
Feb 12, 2025
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
2 changes: 1 addition & 1 deletion barretenberg/cpp/src/barretenberg/bb/exec_pipe.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ inline std::vector<uint8_t> exec_pipe(std::string const& command)
{
FILE* pipe = popen(command.c_str(), "r");
if (!pipe) {
throw std::runtime_error("popen() failed!");
throw std::runtime_error("popen() failed! Can't run: " + command);
}

std::vector<uint8_t> result;
Expand Down
2 changes: 1 addition & 1 deletion barretenberg/cpp/src/barretenberg/bb/get_bn254_crs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ std::vector<g1::affine_element> get_bn254_g1_data(const std::filesystem::path& p

if (g1_file_size >= num_points * 64 && g1_file_size % 64 == 0) {
vinfo("using cached bn254 crs of size ", std::to_string(g1_file_size / 64), " at ", g1_path);
auto data = read_file(g1_path, g1_file_size);
auto data = read_file(g1_path, num_points * 64);
auto points = std::vector<g1::affine_element>(num_points);
for (size_t i = 0; i < num_points; ++i) {
points[i] = from_buffer<g1::affine_element>(data, i * 64);
Expand Down
20 changes: 13 additions & 7 deletions barretenberg/cpp/src/barretenberg/bb/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -910,17 +910,23 @@ UltraProver_<Flavor> compute_valid_prover(const std::string& bytecodePath,
} else if constexpr (IsAnyOf<Flavor, UltraRollupFlavor>) {
honk_recursion = 2;
}
const acir_format::ProgramMetadata metadata{ .recursive = recursive, .honk_recursion = honk_recursion };

acir_format::AcirProgram program{ get_constraint_system(bytecodePath, metadata.honk_recursion) };
if (!witnessPath.empty()) {
program.witness = get_witness(witnessPath);
}
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1180): Don't init grumpkin crs when unnecessary.
init_grumpkin_crs(1 << CONST_ECCVM_LOG_N);

auto builder = acir_format::create_circuit<Builder>(program, metadata);
auto prover = Prover{ builder };
// Lambda function to ensure the builder gets freed before generating the vk. Vk generation requires initialing the
// pippenger runtime state which leads to it being the peak, when its functionality is purely for debugging purposes
// here.
auto prover = [&] {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add your comment about using a lambda to ensure the builder is freed to this usage? I think this is the only place its missed. Definitely good to have so someone doesn't undo it without realizing the effect

const acir_format::ProgramMetadata metadata{ .recursive = recursive, .honk_recursion = honk_recursion };
acir_format::AcirProgram program{ get_constraint_system(bytecodePath, metadata.honk_recursion) };
if (!witnessPath.empty()) {
program.witness = get_witness(witnessPath);
}
auto builder = acir_format::create_circuit<Builder>(program, metadata);
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually I'm a little bit surprised that this helps - It seems like the Prover constructor will instantiate/construct most polynomials so the polys and builder will still coincide. Maybe that's true but it still decreases the peak because we don't simultaneously have builder + polys + sumcheck? I wonder if we'll need something more fine grained in the CIVC setting..

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So this compute_valid_prover function is already allowing us to free the builder before computing the proof. The problem I'm trying to solve here is that we generate the vk in this function, so we create a peak by holding the builder, prover polys, and the commitment key (including the pippenger runtime state).

return Prover{ builder };
}();

size_t required_crs_size = prover.proving_key->proving_key.circuit_size;
if constexpr (Flavor::HasZK) {
// Ensure there are enough points to commit to the libra polynomials required for zero-knowledge sumcheck
Expand Down
72 changes: 40 additions & 32 deletions barretenberg/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,16 +316,18 @@ WASM_EXPORT void acir_prove_ultra_honk(uint8_t const* acir_vec,
uint8_t const* witness_vec,
uint8_t** out)
{
const acir_format::ProgramMetadata metadata{ .recursive = *recursive, .honk_recursion = 1 };

acir_format::AcirProgram program{
acir_format::circuit_buf_to_acir_format(from_buffer<std::vector<uint8_t>>(acir_vec), metadata.honk_recursion),
acir_format::witness_buf_to_witness_data(from_buffer<std::vector<uint8_t>>(witness_vec))
};
// Lambda function to ensure things get freed before proving.
UltraProver prover = [&] {
const acir_format::ProgramMetadata metadata{ .recursive = *recursive, .honk_recursion = 1 };
acir_format::AcirProgram program{ acir_format::circuit_buf_to_acir_format(
from_buffer<std::vector<uint8_t>>(acir_vec), metadata.honk_recursion),
acir_format::witness_buf_to_witness_data(
from_buffer<std::vector<uint8_t>>(witness_vec)) };
auto builder = acir_format::create_circuit<UltraCircuitBuilder>(program, metadata);

return UltraProver(builder);
}();

auto builder = acir_format::create_circuit<UltraCircuitBuilder>(program, metadata);

UltraProver prover{ builder };
auto proof = prover.construct_proof();
*out = to_heap_buffer(to_buffer</*include_size=*/true>(proof));
}
Expand All @@ -335,14 +337,17 @@ WASM_EXPORT void acir_prove_ultra_keccak_honk(uint8_t const* acir_vec,
uint8_t const* witness_vec,
uint8_t** out)
{
auto constraint_system =
acir_format::circuit_buf_to_acir_format(from_buffer<std::vector<uint8_t>>(acir_vec), /*honk_recursion=*/1);
auto witness = acir_format::witness_buf_to_witness_data(from_buffer<std::vector<uint8_t>>(witness_vec));

auto builder = acir_format::create_circuit<UltraCircuitBuilder>(
constraint_system, *recursive, 0, witness, /*honk_recursion=*/1);

UltraKeccakProver prover{ builder };
// Lambda function to ensure things get freed before proving.
UltraKeccakProver prover = [&] {
const acir_format::ProgramMetadata metadata{ .recursive = *recursive, .honk_recursion = 1 };
acir_format::AcirProgram program{ acir_format::circuit_buf_to_acir_format(
from_buffer<std::vector<uint8_t>>(acir_vec), metadata.honk_recursion),
acir_format::witness_buf_to_witness_data(
from_buffer<std::vector<uint8_t>>(witness_vec)) };
auto builder = acir_format::create_circuit<UltraCircuitBuilder>(program, metadata);

return UltraKeccakProver(builder);
}();
auto proof = prover.construct_proof();
*out = to_heap_buffer(to_buffer</*include_size=*/true>(proof));
}
Expand Down Expand Up @@ -381,15 +386,16 @@ WASM_EXPORT void acir_write_vk_ultra_honk(uint8_t const* acir_vec, bool const* r
{
using DeciderProvingKey = DeciderProvingKey_<UltraFlavor>;
using VerificationKey = UltraFlavor::VerificationKey;

const acir_format::ProgramMetadata metadata{ .recursive = *recursive, .honk_recursion = 1 };

acir_format::AcirProgram program{ acir_format::circuit_buf_to_acir_format(
from_buffer<std::vector<uint8_t>>(acir_vec), metadata.honk_recursion) };
auto builder = acir_format::create_circuit<UltraCircuitBuilder>(program, metadata);

DeciderProvingKey proving_key(builder);
// lambda to free the builder
DeciderProvingKey proving_key = [&] {
const acir_format::ProgramMetadata metadata{ .recursive = *recursive, .honk_recursion = 1 };
acir_format::AcirProgram program{ acir_format::circuit_buf_to_acir_format(
from_buffer<std::vector<uint8_t>>(acir_vec), metadata.honk_recursion) };
auto builder = acir_format::create_circuit<UltraCircuitBuilder>(program, metadata);
return DeciderProvingKey(builder);
}();
VerificationKey vk(proving_key.proving_key);
vinfo("Constructed UltraHonk verification key");
*out = to_heap_buffer(to_buffer(vk));
}

Expand All @@ -398,14 +404,16 @@ WASM_EXPORT void acir_write_vk_ultra_keccak_honk(uint8_t const* acir_vec, bool c
using DeciderProvingKey = DeciderProvingKey_<UltraKeccakFlavor>;
using VerificationKey = UltraKeccakFlavor::VerificationKey;

const acir_format::ProgramMetadata metadata{ .recursive = *recursive, .honk_recursion = 1 };

acir_format::AcirProgram program{ acir_format::circuit_buf_to_acir_format(
from_buffer<std::vector<uint8_t>>(acir_vec), metadata.honk_recursion) };
auto builder = acir_format::create_circuit<UltraCircuitBuilder>(program, metadata);

DeciderProvingKey proving_key(builder);
// lambda to free the builder
DeciderProvingKey proving_key = [&] {
const acir_format::ProgramMetadata metadata{ .recursive = *recursive, .honk_recursion = 1 };
acir_format::AcirProgram program{ acir_format::circuit_buf_to_acir_format(
from_buffer<std::vector<uint8_t>>(acir_vec), metadata.honk_recursion) };
auto builder = acir_format::create_circuit<UltraCircuitBuilder>(program, metadata);
return DeciderProvingKey(builder);
}();
VerificationKey vk(proving_key.proving_key);
vinfo("Constructed UltraKeccakHonk verification key");
*out = to_heap_buffer(to_buffer(vk));
}

Expand Down
16 changes: 16 additions & 0 deletions barretenberg/cpp/src/barretenberg/eccvm/eccvm_transcript.test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -278,13 +278,16 @@ TEST_F(ECCVMTranscriptTests, ProverManifestConsistency)

// Automatically generate a transcript manifest by constructing a proof
ECCVMProver prover(builder);
prover.transcript->enable_manifest();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

explicitly enable prover manifest for this test

prover.ipa_transcript->enable_manifest();
ECCVMProof proof = prover.construct_proof();

// Check that the prover generated manifest agrees with the manifest hard coded in this suite
auto manifest_expected = this->construct_eccvm_honk_manifest();
auto prover_manifest = prover.transcript->get_manifest();

// Note: a manifest can be printed using manifest.print()
ASSERT(manifest_expected.size() > 0);
for (size_t round = 0; round < manifest_expected.size(); ++round) {
ASSERT_EQ(prover_manifest[round], manifest_expected[round]) << "Prover manifest discrepency in round " << round;
}
Expand All @@ -293,6 +296,7 @@ TEST_F(ECCVMTranscriptTests, ProverManifestConsistency)
auto prover_ipa_manifest = prover.ipa_transcript->get_manifest();

// Note: a manifest can be printed using manifest.print()
ASSERT(ipa_manifest_expected.size() > 0);
for (size_t round = 0; round < ipa_manifest_expected.size(); ++round) {
ASSERT_EQ(prover_ipa_manifest[round], ipa_manifest_expected[round])
<< "IPA prover manifest discrepency in round " << round;
Expand All @@ -311,6 +315,8 @@ TEST_F(ECCVMTranscriptTests, VerifierManifestConsistency)

// Automatically generate a transcript manifest in the prover by constructing a proof
ECCVMProver prover(builder);
prover.transcript->enable_manifest();
prover.ipa_transcript->enable_manifest();
ECCVMProof proof = prover.construct_proof();

// Automatically generate a transcript manifest in the verifier by verifying a proof
Expand All @@ -325,10 +331,20 @@ TEST_F(ECCVMTranscriptTests, VerifierManifestConsistency)
// The last challenge generated by the ECCVM Prover is the translation univariate batching challenge and, on the
// verifier side, is only generated in the translator verifier hence the ECCVM prover's manifest will have one extra
// challenge
ASSERT(prover_manifest.size() > 0);
for (size_t round = 0; round < prover_manifest.size() - 1; ++round) {
ASSERT_EQ(prover_manifest[round], verifier_manifest[round])
<< "Prover/Verifier manifest discrepency in round " << round;
}

// Check consistency of IPA transcripts
auto prover_ipa_manifest = prover.ipa_transcript->get_manifest();
auto verifier_ipa_manifest = verifier.ipa_transcript->get_manifest();
ASSERT(prover_ipa_manifest.size() > 0);
for (size_t round = 0; round < prover_ipa_manifest.size(); ++round) {
ASSERT_EQ(prover_ipa_manifest[round], verifier_ipa_manifest[round])
<< "Prover/Verifier IPA manifest discrepency in round " << round;
}
}

/**
Expand Down
3 changes: 3 additions & 0 deletions barretenberg/cpp/src/barretenberg/eccvm/eccvm_verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ bool ECCVMVerifier::verify_proof(const ECCVMProof& proof)
RelationParameters<FF> relation_parameters;
transcript = std::make_shared<Transcript>(proof.pre_ipa_proof);
ipa_transcript = std::make_shared<Transcript>(proof.ipa_proof);
transcript->enable_manifest();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

just enable the manifests in the native/recursive verifiers since we shouldn't be having memory problems in those scenarios

Copy link
Contributor Author

Choose a reason for hiding this comment

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

its a little ugly that we have to add this though

Copy link
Contributor

Choose a reason for hiding this comment

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

it doesnt seem too bad. I dont know, my sense was that the manifest was useful but if we find that it isnt maybe we'll eventually just kill it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah its not horrible since we don't really use the manifest outside of the tests. If we ever want to test the manifest more, it shouldn't be too bad to add a few more enable_manifest()s since I'm pretty I'm missing a few in some verifiers where the manifest isn't tested.

ipa_transcript->enable_manifest();

VerifierCommitments commitments{ key };
CommitmentLabels commitment_labels;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ void ProtogalaxyVerifier_<DeciderVerificationKeys>::run_oink_verifier_on_each_in
const std::vector<FF>& proof)
{
transcript = std::make_shared<Transcript>(proof);
transcript->enable_manifest();
size_t index = 0;
auto key = keys_to_fold[0];
auto domain_separator = std::to_string(index);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ ECCVMRecursiveVerifier_<Flavor>::verify_proof(const ECCVMProof& proof)
StdlibProof<Builder> stdlib_ipa_proof = bb::convert_native_proof_to_stdlib(builder, proof.ipa_proof);
transcript = std::make_shared<Transcript>(stdlib_proof);
ipa_transcript = std::make_shared<Transcript>(stdlib_ipa_proof);
transcript->enable_manifest();
ipa_transcript->enable_manifest();

VerifierCommitments commitments{ key };
CommitmentLabels commitment_labels;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ template <typename RecursiveFlavor> class ECCVMRecursiveTests : public ::testing
EXPECT_TRUE(native_result);
auto recursive_manifest = verifier.transcript->get_manifest();
auto native_manifest = native_verifier.transcript->get_manifest();

ASSERT(recursive_manifest.size() > 0);
for (size_t i = 0; i < recursive_manifest.size(); ++i) {
EXPECT_EQ(recursive_manifest[i], native_manifest[i])
<< "Recursive Verifier/Verifier manifest discrepency in round " << i;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ void ProtogalaxyRecursiveVerifier_<DeciderVerificationKeys>::run_oink_verifier_o
const std::vector<FF>& proof)
{
transcript = std::make_shared<Transcript>(proof);
transcript->enable_manifest();
size_t index = 0;
auto key = keys_to_fold[0];
auto domain_separator = std::to_string(index);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ template <typename RecursiveFlavor> class ProtogalaxyRecursiveTests : public tes
auto recursive_folding_manifest = verifier.transcript->get_manifest();
auto native_folding_manifest = native_folding_verifier.transcript->get_manifest();

ASSERT(recursive_folding_manifest.size() > 0);
for (size_t i = 0; i < recursive_folding_manifest.size(); ++i) {
EXPECT_EQ(recursive_folding_manifest[i], native_folding_manifest[i])
<< "Recursive Verifier/Verifier manifest discrepency in round " << i;
Expand Down Expand Up @@ -316,6 +317,7 @@ template <typename RecursiveFlavor> class ProtogalaxyRecursiveTests : public tes
auto recursive_folding_manifest = verifier.transcript->get_manifest();
auto native_folding_manifest = native_folding_verifier.transcript->get_manifest();

ASSERT(recursive_folding_manifest.size() > 0);
for (size_t i = 0; i < recursive_folding_manifest.size(); ++i) {
EXPECT_EQ(recursive_folding_manifest[i], native_folding_manifest[i])
<< "Recursive Verifier/Verifier manifest discrepency in round " << i;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ void TraceToPolynomials<Flavor>::add_memory_records_to_proving_key(TraceData& tr
ASSERT(proving_key.memory_read_records.empty() && proving_key.memory_write_records.empty());

// Update indices of RAM/ROM reads/writes based on where block containing these gates sits in the trace
proving_key.memory_read_records.reserve(builder.memory_read_records.size());
Copy link
Contributor Author

Choose a reason for hiding this comment

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

reserve instead of just emplace_back since we know the size of this vector

for (auto& index : builder.memory_read_records) {
proving_key.memory_read_records.emplace_back(index + trace_data.ram_rom_offset);
}
proving_key.memory_write_records.reserve(builder.memory_write_records.size());
for (auto& index : builder.memory_write_records) {
proving_key.memory_write_records.emplace_back(index + trace_data.ram_rom_offset);
}
Expand Down
28 changes: 23 additions & 5 deletions barretenberg/cpp/src/barretenberg/transcript/transcript.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ template <typename TranscriptParams> class BaseTranscript {
Fr previous_challenge{}; // default-initialized to zeros
std::vector<Fr> current_round_data;

bool use_manifest = false; // indicates whether the manifest is turned on, currently only on for manifest tests.

// "Manifest" object that records a summary of the transcript interactions
TranscriptManifest manifest;

Expand Down Expand Up @@ -211,8 +213,10 @@ template <typename TranscriptParams> class BaseTranscript {
*/
void consume_prover_element_frs(const std::string& label, std::span<const Fr> element_frs)
{
// Add an entry to the current round of the manifest
manifest.add_entry(round_number, label, element_frs.size());
if (use_manifest) {
// Add an entry to the current round of the manifest
manifest.add_entry(round_number, label, element_frs.size());
}

current_round_data.insert(current_round_data.end(), element_frs.begin(), element_frs.end());

Expand Down Expand Up @@ -276,6 +280,12 @@ template <typename TranscriptParams> class BaseTranscript {
std::copy(proof.begin(), proof.end(), std::back_inserter(proof_data));
}

/**
* @brief Enables the manifest
*
*/
void enable_manifest() { use_manifest = true; }

/**
* @brief After all the prover messages have been sent, finalize the round by hashing all the data and then
* create the number of requested challenges.
Expand All @@ -290,8 +300,10 @@ template <typename TranscriptParams> class BaseTranscript {
{
constexpr size_t num_challenges = sizeof...(Strings);

// Add challenge labels for current round to the manifest
manifest.add_challenge(round_number, labels...);
if (use_manifest) {
// Add challenge labels for current round to the manifest
manifest.add_challenge(round_number, labels...);
}

// Compute the new challenge buffer from which we derive the challenges.

Expand Down Expand Up @@ -438,7 +450,13 @@ template <typename TranscriptParams> class BaseTranscript {

[[nodiscard]] TranscriptManifest get_manifest() const { return manifest; };

void print() { manifest.print(); }
void print()
{
if (!use_manifest) {
info("Warning: manifest is not enabled!");
}
manifest.print();
}
};

template <typename Builder>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ template <IsUltraFlavor Flavor> void DeciderProvingKey_<Flavor>::allocate_select
template <IsUltraFlavor Flavor>
void DeciderProvingKey_<Flavor>::allocate_table_lookup_polynomials(const Circuit& circuit)
{
PROFILE_THIS_NAME("allocate_table_lookup_polynomials_and_inverses");
PROFILE_THIS_NAME("allocate_table_lookup_and_lookup_read_polynomials");

size_t table_offset = circuit.blocks.lookup.trace_offset;
// TODO(https://github.com/AztecProtocol/barretenberg/issues/1193): can potentially improve memory footprint
Expand Down Expand Up @@ -143,6 +143,7 @@ template <IsUltraFlavor Flavor>
void DeciderProvingKey_<Flavor>::allocate_databus_polynomials(const Circuit& circuit)
requires HasDataBus<Flavor>
{
PROFILE_THIS_NAME("allocate_databus_and_lookup_inverse_polynomials");
proving_key.polynomials.calldata = Polynomial(MAX_DATABUS_SIZE, proving_key.circuit_size);
proving_key.polynomials.calldata_read_counts = Polynomial(MAX_DATABUS_SIZE, proving_key.circuit_size);
proving_key.polynomials.calldata_read_tags = Polynomial(MAX_DATABUS_SIZE, proving_key.circuit_size);
Expand Down
Loading