Skip to content

Commit

Permalink
chore: minor Gemini refactor to prep for opening k-shifts (#11393)
Browse files Browse the repository at this point in the history
The first in a series of Gemini updates that will facilitate the
addition of k-shifts and hopefully improve the code along the way.

Each method now has a single clear purpose and the storage of
polynomials is general enough to accommodate opening a new set of
polynomials. We make a distinction between the partially evaluated batch
polynomials A₀₊(r), A₀₋(-r), and the d-1 "fold" polynomials Aₗ(−r^{2ˡ}),
l = 1, ..., d-1. The former are constructed via
`compute_partially_evaluated_batch_polynomials` and the latter through
`compute_fold_polynomials`. Univariate opening claims for all d+1
polynomials are constructed through
`construct_univariate_opening_claims`. This makes each method clearer
and avoids the need to store "F" and "G" in the first two slots of the
old `fold_polynomials`, a trick which no longer works once we have a 3rd
polynomial type, i.e. F, G and H.
  • Loading branch information
ledwards2225 authored Jan 22, 2025
1 parent 5a52e95 commit 30a063a
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,22 @@ template <typename Curve> class GeminiProver_ {
using Claim = ProverOpeningClaim<Curve>;

public:
static std::vector<Polynomial> compute_fold_polynomials(const size_t log_N,
static std::vector<Polynomial> compute_fold_polynomials(const size_t log_n,
std::span<const Fr> multilinear_challenge,
Polynomial&& batched_unshifted,
Polynomial&& batched_to_be_shifted,
Polynomial&& batched_concatenated = {});
const Polynomial& A_0);

static std::vector<Claim> compute_fold_polynomial_evaluations(
const size_t log_N,
std::vector<Polynomial>&& fold_polynomials,
static std::pair<Polynomial, Polynomial> compute_partially_evaluated_batch_polynomials(
const size_t log_n,
Polynomial&& batched_F,
Polynomial&& batched_G,
const Fr& r_challenge,
std::vector<Polynomial>&& batched_groups_to_be_concatenated = {});
const std::vector<Polynomial>& batched_groups_to_be_concatenated = {});

static std::vector<Claim> construct_univariate_opening_claims(const size_t log_n,
Polynomial&& A_0_pos,
Polynomial&& A_0_neg,
std::vector<Polynomial>&& fold_polynomials,
const Fr& r_challenge);

template <typename Transcript>
static std::vector<Claim> prove(const Fr circuit_size,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,12 @@ std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::prove(
RefSpan<Polynomial> concatenated_polynomials,
const std::vector<RefVector<Polynomial>>& groups_to_be_concatenated,
bool has_zk)

{
const size_t log_n = numeric::get_msb(static_cast<uint32_t>(circuit_size));
const size_t n = 1 << log_n;

const bool has_concatenations = concatenated_polynomials.size() > 0;

// Compute batched polynomials
Polynomial batched_unshifted(n);
Polynomial batched_to_be_shifted = Polynomial::shiftable(n);
Expand All @@ -78,6 +79,7 @@ std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::prove(
// ρ⁰ is used to batch the hiding polynomial
rho_challenge *= rho;
}

for (size_t i = 0; i < f_polynomials.size(); i++) {
batched_unshifted.add_scaled(f_polynomials[i], rho_challenge);
rho_challenge *= rho;
Expand All @@ -90,34 +92,41 @@ std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::prove(
size_t num_groups = groups_to_be_concatenated.size();
size_t num_chunks_per_group = groups_to_be_concatenated.empty() ? 0 : groups_to_be_concatenated[0].size();

// Allocate space for the groups to be concatenated and for the concatenated polynomials
Polynomial batched_concatenated(n);
// If needed, allocate space for the groups to be concatenated and for the concatenated polynomials
Polynomial batched_concatenated;
std::vector<Polynomial> batched_group;
for (size_t i = 0; i < num_chunks_per_group; ++i) {
batched_group.push_back(Polynomial(n));
}
if (has_concatenations) {
batched_concatenated = Polynomial(n);
for (size_t i = 0; i < num_chunks_per_group; ++i) {
batched_group.push_back(Polynomial(n));
}

for (size_t i = 0; i < num_groups; ++i) {
batched_concatenated.add_scaled(concatenated_polynomials[i], rho_challenge);
for (size_t j = 0; j < num_chunks_per_group; ++j) {
batched_group[j].add_scaled(groups_to_be_concatenated[i][j], rho_challenge);
for (size_t i = 0; i < num_groups; ++i) {
batched_concatenated.add_scaled(concatenated_polynomials[i], rho_challenge);
for (size_t j = 0; j < num_chunks_per_group; ++j) {
batched_group[j].add_scaled(groups_to_be_concatenated[i][j], rho_challenge);
}
rho_challenge *= rho;
}
rho_challenge *= rho;
}

auto fold_polynomials = compute_fold_polynomials(log_n,
multilinear_challenge,
std::move(batched_unshifted),
std::move(batched_to_be_shifted),
std::move(batched_concatenated));
// Construct the batched polynomial A₀(X) = F(X) + G↺(X) = F(X) + G(X)/X
Polynomial A_0 = batched_unshifted;
A_0 += batched_to_be_shifted.shifted();
if (has_concatenations) { // If proving for translator, add contribution of the batched concatenation polynomials
A_0 += batched_concatenated;
}

// Construct the d-1 Gemini foldings of A₀(X)
std::vector<Polynomial> fold_polynomials = compute_fold_polynomials(log_n, multilinear_challenge, A_0);

// TODO(https://github.com/AztecProtocol/barretenberg/issues/1159): Decouple constants from primitives.
for (size_t l = 0; l < CONST_PROOF_SIZE_LOG_N - 1; l++) {
std::string label = "Gemini:FOLD_" + std::to_string(l + 1);
if (l < log_n - 1) {
transcript->send_to_verifier("Gemini:FOLD_" + std::to_string(l + 1),
commitment_key->commit(fold_polynomials[l + 2]));
transcript->send_to_verifier(label, commitment_key->commit(fold_polynomials[l]));
} else {
transcript->send_to_verifier("Gemini:FOLD_" + std::to_string(l + 1), Commitment::one());
transcript->send_to_verifier(label, Commitment::one());
}
}
const Fr r_challenge = transcript->template get_challenge<Fr>("Gemini:r");
Expand All @@ -131,14 +140,20 @@ std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::prove(
throw_or_abort("Gemini evaluation challenge is in the SmallSubgroup.");
}

std::vector<Claim> claims =
compute_fold_polynomial_evaluations(log_n, std::move(fold_polynomials), r_challenge, std::move(batched_group));
// Compute polynomials A₀₊(X) = F(X) + G(X)/r and A₀₋(X) = F(X) - G(X)/r
auto [A_0_pos, A_0_neg] = compute_partially_evaluated_batch_polynomials(
log_n, std::move(batched_unshifted), std::move(batched_to_be_shifted), r_challenge, batched_group);

// Construct claims for the d + 1 univariate evaluations A₀₊(r), A₀₋(-r), and Foldₗ(−r^{2ˡ}), l = 1, ..., d-1
std::vector<Claim> claims = construct_univariate_opening_claims(
log_n, std::move(A_0_pos), std::move(A_0_neg), std::move(fold_polynomials), r_challenge);

for (size_t l = 1; l <= CONST_PROOF_SIZE_LOG_N; l++) {
std::string label = "Gemini:a_" + std::to_string(l);
if (l <= log_n) {
transcript->send_to_verifier("Gemini:a_" + std::to_string(l), claims[l].opening_pair.evaluation);
transcript->send_to_verifier(label, claims[l].opening_pair.evaluation);
} else {
transcript->send_to_verifier("Gemini:a_" + std::to_string(l), Fr::zero());
transcript->send_to_verifier(label, Fr::zero());
}
}

Expand All @@ -148,48 +163,24 @@ std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::prove(
/**
* @brief Computes d-1 fold polynomials Fold_i, i = 1, ..., d-1
*
* @param mle_opening_point multilinear opening point 'u'
* @param batched_unshifted F(X) = ∑ⱼ ρʲ fⱼ(X) .
* @param batched_to_be_shifted G(X) = ∑ⱼ ρᵏ⁺ʲ gⱼ(X)
* @param batched_concatenated The sum of batched concatenated polynomial,
* @param multilinear_challenge multilinear opening point 'u'
* @param A_0 = F(X) + G↺(X) = F(X) + G(X)/X
* @return std::vector<Polynomial>
*/
template <typename Curve>
std::vector<typename GeminiProver_<Curve>::Polynomial> GeminiProver_<Curve>::compute_fold_polynomials(
const size_t num_variables,
std::span<const Fr> mle_opening_point,
Polynomial&& batched_unshifted,
Polynomial&& batched_to_be_shifted,
Polynomial&& batched_concatenated)
const size_t log_n, std::span<const Fr> multilinear_challenge, const Polynomial& A_0)
{
const size_t num_threads = get_num_cpus_pow2();
constexpr size_t efficient_operations_per_thread = 64; // A guess of the number of operation for which there
// would be a point in sending them to a separate thread

// Allocate space for m+1 Fold polynomials
//
// The first two are populated here with the batched unshifted and to-be-shifted polynomial respectively.
// They will eventually contain the full batched polynomial A₀ partially evaluated at the challenges r,-r.
// This function populates the other m-1 polynomials with the foldings of A₀.
// Reserve and allocate space for m-1 Fold polynomials, the foldings of the full batched polynomial A₀
std::vector<Polynomial> fold_polynomials;
fold_polynomials.reserve(num_variables + 1);

// F(X) = ∑ⱼ ρʲ fⱼ(X) and G(X) = ∑ⱼ ρᵏ⁺ʲ gⱼ(X)
Polynomial& batched_F = fold_polynomials.emplace_back(std::move(batched_unshifted));
Polynomial& batched_G = fold_polynomials.emplace_back(std::move(batched_to_be_shifted));
constexpr size_t offset_to_folded = 2; // Offset because of F an G
// A₀(X) = F(X) + G↺(X) = F(X) + G(X)/X.
Polynomial A_0 = batched_F;

// If proving the opening for translator, add a non-zero contribution of the batched concatenation polynomials
A_0 += batched_concatenated;

A_0 += batched_G.shifted();

// Allocate everything before parallel computation
for (size_t l = 0; l < num_variables - 1; ++l) {
fold_polynomials.reserve(log_n - 1);
for (size_t l = 0; l < log_n - 1; ++l) {
// size of the previous polynomial/2
const size_t n_l = 1 << (num_variables - l - 1);
const size_t n_l = 1 << (log_n - l - 1);

// A_l_fold = Aₗ₊₁(X) = (1-uₗ)⋅even(Aₗ)(X) + uₗ⋅odd(Aₗ)(X)
fold_polynomials.emplace_back(Polynomial(n_l));
Expand All @@ -199,9 +190,9 @@ std::vector<typename GeminiProver_<Curve>::Polynomial> GeminiProver_<Curve>::com
// in the first iteration, we take the batched polynomial
// in the next iteration, it is the previously folded one
auto A_l = A_0.data();
for (size_t l = 0; l < num_variables - 1; ++l) {
for (size_t l = 0; l < log_n - 1; ++l) {
// size of the previous polynomial/2
const size_t n_l = 1 << (num_variables - l - 1);
const size_t n_l = 1 << (log_n - l - 1);

// Use as many threads as it is useful so that 1 thread doesn't process 1 element, but make sure that there is
// at least 1
Expand All @@ -210,11 +201,11 @@ std::vector<typename GeminiProver_<Curve>::Polynomial> GeminiProver_<Curve>::com
size_t chunk_size = n_l / num_used_threads;
size_t last_chunk_size = (n_l % chunk_size) ? (n_l % num_used_threads) : chunk_size;

// Openning point is the same for all
const Fr u_l = mle_opening_point[l];
// Opening point is the same for all
const Fr u_l = multilinear_challenge[l];

// A_l_fold = Aₗ₊₁(X) = (1-uₗ)⋅even(Aₗ)(X) + uₗ⋅odd(Aₗ)(X)
auto A_l_fold = fold_polynomials[l + offset_to_folded].data();
auto A_l_fold = fold_polynomials[l].data();

parallel_for(num_used_threads, [&](size_t i) {
size_t current_chunk_size = (i == (num_used_threads - 1)) ? last_chunk_size : chunk_size;
Expand All @@ -235,51 +226,31 @@ std::vector<typename GeminiProver_<Curve>::Polynomial> GeminiProver_<Curve>::com
};

/**
* @brief Computes/aggragates d+1 Fold polynomials and their opening pairs (challenge, evaluation)
*
* @details This function assumes that, upon input, last d-1 entries in fold_polynomials are Fold_i.
* The first two entries are assumed to be, respectively, the batched unshifted and batched to-be-shifted
* polynomials F(X) = ∑ⱼ ρʲfⱼ(X) and G(X) = ∑ⱼ ρᵏ⁺ʲ gⱼ(X). This function completes the computation
* of the first two Fold polynomials as F + G/r and F - G/r. It then evaluates each of the d+1
* fold polynomials at, respectively, the points r, rₗ = r^{2ˡ} for l = 0, 1, ..., d-1.
* @brief Computes partially evaluated batched polynomials A₀₊(X) = F(X) + G(X)/r and A₀₋(X) = F(X) - G(X)/r
*
* @param mle_opening_point u = (u₀,...,uₘ₋₁) is the MLE opening point
* @param fold_polynomials vector of polynomials whose first two elements are F(X) = ∑ⱼ ρʲfⱼ(X)
* and G(X) = ∑ⱼ ρᵏ⁺ʲ gⱼ(X), and the next d-1 elements are Fold_i, i = 1, ..., d-1.
* @param r_challenge univariate opening challenge
* @param batched_F F(X) = ∑ⱼ ρʲfⱼ(X)
* @param batched_G G(X) = ∑ⱼ ρᵏ⁺ʲ gⱼ(X)
* @param r_challenge
* @param batched_groups_to_be_concatenated
* @return {A₀₊(X), A₀₋(X)}
*/
template <typename Curve>
std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::compute_fold_polynomial_evaluations(
const size_t num_variables,
std::vector<Polynomial>&& fold_polynomials,
const Fr& r_challenge,
std::vector<Polynomial>&& batched_groups_to_be_concatenated)
std::pair<typename GeminiProver_<Curve>::Polynomial, typename GeminiProver_<Curve>::Polynomial> GeminiProver_<Curve>::
compute_partially_evaluated_batch_polynomials(const size_t log_n,
Polynomial&& batched_F,
Polynomial&& batched_G,
const Fr& r_challenge,
const std::vector<Polynomial>& batched_groups_to_be_concatenated)
{

Polynomial& batched_F = fold_polynomials[0]; // F(X) = ∑ⱼ ρʲ fⱼ(X)

Polynomial& batched_G = fold_polynomials[1]; // G(X) = ∑ⱼ ρᵏ⁺ʲ gⱼ(X)

// Compute univariate opening queries rₗ = r^{2ˡ} for l = 0, 1, ..., m-1
std::vector<Fr> r_squares = gemini::powers_of_evaluation_challenge(r_challenge, num_variables);
Polynomial& A_0_pos = batched_F; // A₀₊ = F
Polynomial A_0_neg = batched_F; // A₀₋ = F

// Compute G/r
Fr r_inv = r_challenge.invert();
batched_G *= r_inv;

// Construct A₀₊ = F + G/r and A₀₋ = F - G/r in place in fold_polynomials
Polynomial tmp = batched_F;
Polynomial& A_0_pos = fold_polynomials[0];

// A₀₊(X) = F(X) + G(X)/r, s.t. A₀₊(r) = A₀(r)
A_0_pos += batched_G;

// Perform a swap so that tmp = G(X)/r and A_0_neg = F(X)
std::swap(tmp, batched_G);
Polynomial& A_0_neg = fold_polynomials[1];

// A₀₋(X) = F(X) - G(X)/r, s.t. A₀₋(-r) = A₀(-r)
A_0_neg -= tmp;
A_0_pos += batched_G; // A₀₊ = F + G/r
A_0_neg -= batched_G; // A₀₋ = F - G/r

// Reconstruct the batched concatenated polynomial from the batched groups, partially evaluated at r and -r and add
// the result to A₀₊(X) and A₀₋(X). Explanation (for simplification assume a single concatenated polynomial):
Expand All @@ -292,7 +263,7 @@ std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::compute_
// P
if (!batched_groups_to_be_concatenated.empty()) {
// The "real" size of polynomials in concatenation groups (i.e. the number of non-zero values)
const size_t mini_circuit_size = (1 << num_variables) / batched_groups_to_be_concatenated.size();
const size_t mini_circuit_size = (1 << log_n) / batched_groups_to_be_concatenated.size();
Fr current_r_shift_pos = Fr(1);
Fr current_r_shift_neg = Fr(1);

Expand All @@ -308,18 +279,57 @@ std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::compute_
}
}

std::vector<Claim> opening_claims;
opening_claims.reserve(num_variables + 1);
return { std::move(A_0_pos), std::move(A_0_neg) };
};

// Compute first opening pair {r, A₀(r)}
Fr evaluation = fold_polynomials[0].evaluate(r_challenge);
opening_claims.emplace_back(Claim{ fold_polynomials[0], { r_challenge, evaluation } });
// Compute the remaining m opening pairs {−r^{2ˡ}, Aₗ(−r^{2ˡ})}, l = 0, ..., m-1.
for (size_t l = 0; l < num_variables; ++l) {
evaluation = fold_polynomials[l + 1].evaluate(-r_squares[l]);
opening_claims.emplace_back(Claim{ fold_polynomials[l + 1], { -r_squares[l], evaluation } });
/**
*
* @param mle_opening_point u = (u₀,...,uₘ₋₁) is the MLE opening point
* @param fold_polynomials vector of polynomials whose first two elements are F(X) = ∑ⱼ ρʲfⱼ(X)
* and G(X) = ∑ⱼ ρᵏ⁺ʲ gⱼ(X), and the next d-1 elements are Fold_i, i = 1, ..., d-1.
* @param r_challenge univariate opening challenge
*/

/**
* @brief Computes/aggragates d+1 univariate polynomial opening claims of the form {polynomial, (challenge, evaluation)}
*
* @details The d+1 evaluations are A₀₊(r), A₀₋(-r), and Aₗ(−r^{2ˡ}) for l = 1, ..., d-1, where the Aₗ are the fold
* polynomials.
*
* @param A_0_pos A₀₊
* @param A_0_neg A₀₋
* @param fold_polynomials Aₗ, l = 1, ..., d-1
* @param r_challenge
* @return std::vector<typename GeminiProver_<Curve>::Claim> d+1 univariate opening claims
*/
template <typename Curve>
std::vector<typename GeminiProver_<Curve>::Claim> GeminiProver_<Curve>::construct_univariate_opening_claims(
const size_t log_n,
Polynomial&& A_0_pos,
Polynomial&& A_0_neg,
std::vector<Polynomial>&& fold_polynomials,
const Fr& r_challenge)
{
std::vector<Claim> claims;

// Compute evaluation of partially evaluated batch polynomial (positive) A₀₊(r)
Fr a_0_pos = A_0_pos.evaluate(r_challenge);
claims.emplace_back(Claim{ std::move(A_0_pos), { r_challenge, a_0_pos } });
// Compute evaluation of partially evaluated batch polynomial (negative) A₀₋(-r)
Fr a_0_neg = A_0_neg.evaluate(-r_challenge);
claims.emplace_back(Claim{ std::move(A_0_neg), { -r_challenge, a_0_neg } });

// Compute univariate opening queries rₗ = r^{2ˡ} for l = 0, 1, ..., m-1
std::vector<Fr> r_squares = gemini::powers_of_evaluation_challenge(r_challenge, log_n);

// Compute the remaining m opening pairs {−r^{2ˡ}, Aₗ(−r^{2ˡ})}, l = 1, ..., m-1.
for (size_t l = 0; l < log_n - 1; ++l) {
Fr evaluation = fold_polynomials[l].evaluate(-r_squares[l + 1]);
claims.emplace_back(Claim{ std::move(fold_polynomials[l]), { -r_squares[l + 1], evaluation } });
}

return opening_claims;
return claims;
};

} // namespace bb
Loading

1 comment on commit 30a063a

@AztecBot
Copy link
Collaborator

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'C++ Benchmark'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.05.

Benchmark suite Current: 30a063a Previous: 5a52e95 Ratio
nativeconstruct_proof_ultrahonk_power_of_2/20 4520.827936999978 ms/iter 4067.51341399999 ms/iter 1.11
commit(t) 3284759030 ns/iter 3098157138 ns/iter 1.06
Goblin::merge(t) 150311423 ns/iter 133538618 ns/iter 1.13

This comment was automatically generated by workflow using github-action-benchmark.

CC: @ludamad @codygunton

Please sign in to comment.