Skip to content

Commit

Permalink
feat: added a UnivariateMonomial representation to reduce field ops i…
Browse files Browse the repository at this point in the history
…n protogalaxy+sumcheck (#10401)

Summary:

`client_ivc_bench.sh` benchmark has been improved by approx 10% (26218ms
vs 29306ms)

In both protogalaxy + sumcheck, the basic representation of the edge of
the boolean hypercube is now a degree-1 monomial instead of a
MAX_RELATION_DEGREE-degree monomial

The class UnivariateMonomial can efficiently evaluate low-degree
monomial relations of up to degree-2. The relations in the `relations`
directory have been reworked to perform initial low-degree algebraic
computations using UnivariateMonomial, only converting to a full
Monomial object once the UnivariateMonomial would otherwise exceed
degree-2

Reason why we do all of this:

1. for MegaFlavor, `extend_edges` was converting every flavour
polynomial into a degree-11 Univariate. This was introducing 9 Fp
additions * NUM_ALL_ENTITIES per row in the circuit. Given the sparse
trace structure we are working with, this is a lot of computation that
this PR makes redundant
2. for each relation, we check if it can be skipped by typically calling
`is_zero` on a selector. The selector poly is in Univariate form
(MegaFlavor = degree-11) which is 11 Fp zero-checks. MegaFlavor has 9
skippable relations which is 99 Fp zero-checks. With the new degree-2
representation this is reduced to only 18 Fp zero-checks
3. The number of raw Fp add and mul operations required to evaluate our
relations is reduced. For example, in the permutation argument each
`*`/`+` operation in the `accumulate` function was costing us 11 Fp
muls/adds. It is cheaper to compute low-degree sub-terms in the
coefficient representation before extend inginto point-evaluation
representation

e.g. consider (in the protogalaxy case where challenges are degree-1
univariates) `(w_i + \beta * S_i + \gamma)` for `i = 0,1,2,3`. In
coefficient representation this term can be computed with 8 Fp adds and
3 Fp muls. Extending into a degree-11 point evaluation form costs 18 Fp
adds for a total of 26 Fp adds and 3 Fp muls.

In master branch, using Univariate<11> this computation costs us 20 Fp
adds and 10 Fp muls. Assuming an add is 1/3 the cost of a mul, this
makes the new approach cost 35 Fp add-equivalent operations vs 50 Fp
add-equivalent

Overall in the new approach, the number of field operations to compute
the permutation argument has reduced by 30%
  • Loading branch information
zac-williamson authored Dec 19, 2024
1 parent 8b4387d commit 15475f4
Show file tree
Hide file tree
Showing 34 changed files with 1,288 additions and 463 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ template <typename Flavor, typename Relation> void execute_relation_for_univaria
template <typename Flavor, typename Relation> void execute_relation_for_pg_univariates(::benchmark::State& state)
{
using DeciderProvingKeys = DeciderProvingKeys_<Flavor>;
using Input = ProtogalaxyProverInternal<DeciderProvingKeys>::ExtendedUnivariatesNoOptimisticSkipping;
using Accumulator = typename Relation::template ProtogalaxyTupleOfUnivariatesOverSubrelationsNoOptimisticSkipping<
DeciderProvingKeys::NUM>;
using Input = ProtogalaxyProverInternal<DeciderProvingKeys>::ExtendedUnivariates;
using Accumulator =
typename Relation::template ProtogalaxyTupleOfUnivariatesOverSubrelations<DeciderProvingKeys::NUM>;

execute_relation<Flavor, Relation, Input, Accumulator>(state);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ namespace bb {
template <class Params_> struct alignas(32) field {
public:
using View = field;
using CoefficientAccumulator = field;
using Params = Params_;
using in_buf = const uint8_t*;
using vec_in_buf = const uint8_t*;
Expand Down
3 changes: 3 additions & 0 deletions barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class ECCVMFlavor {
using RelationSeparator = FF;
using MSM = bb::eccvm::MSM<CycleGroup>;

// indicates when evaluating sumcheck, edges must be extended to be MAX_TOTAL_RELATION_LENGTH
static constexpr bool USE_SHORT_MONOMIALS = false;

// Indicates that this flavor runs with ZK Sumcheck.
static constexpr bool HasZK = true;
static constexpr size_t NUM_WIRES = 85;
Expand Down
94 changes: 90 additions & 4 deletions barretenberg/cpp/src/barretenberg/polynomials/univariate.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "barretenberg/common/assert.hpp"
#include "barretenberg/common/serialize.hpp"
#include "barretenberg/polynomials/barycentric.hpp"
#include "barretenberg/polynomials/univariate_coefficient_basis.hpp"
#include <span>

namespace bb {
Expand Down Expand Up @@ -30,6 +31,8 @@ template <class Fr, size_t domain_end, size_t domain_start = 0, size_t skip_coun
static constexpr size_t LENGTH = domain_end - domain_start;
static constexpr size_t SKIP_COUNT = skip_count;
using View = UnivariateView<Fr, domain_end, domain_start, skip_count>;
static constexpr size_t MONOMIAL_LENGTH = LENGTH > 1 ? 2 : 1;
using CoefficientAccumulator = UnivariateCoefficientBasis<Fr, MONOMIAL_LENGTH, true>;

using value_type = Fr; // used to get the type of the elements consistently with std::array

Expand All @@ -47,6 +50,58 @@ template <class Fr, size_t domain_end, size_t domain_start = 0, size_t skip_coun
Univariate& operator=(const Univariate& other) = default;
Univariate& operator=(Univariate&& other) noexcept = default;

explicit operator UnivariateCoefficientBasis<Fr, 2, true>() const
requires(LENGTH > 1)
{
static_assert(domain_end >= 2);
static_assert(domain_start == 0);

UnivariateCoefficientBasis<Fr, 2, true> result;
result.coefficients[0] = evaluations[0];
result.coefficients[1] = evaluations[1] - evaluations[0];
result.coefficients[2] = evaluations[1];
return result;
}

template <bool has_a0_plus_a1> Univariate(UnivariateCoefficientBasis<Fr, 2, has_a0_plus_a1> monomial)
{
static_assert(domain_start == 0);
Fr to_add = monomial.coefficients[1];
evaluations[0] = monomial.coefficients[0];
auto prev = evaluations[0];
for (size_t i = 1; i < skip_count + 1; ++i) {
evaluations[i] = 0;
prev = prev + to_add;
}

for (size_t i = skip_count + 1; i < domain_end; ++i) {
prev = prev + to_add;
evaluations[i] = prev;
}
}

template <bool has_a0_plus_a1> Univariate(UnivariateCoefficientBasis<Fr, 3, has_a0_plus_a1> monomial)
{
static_assert(domain_start == 0);
Fr to_add = monomial.coefficients[1]; // a1 + a2
Fr derivative = monomial.coefficients[2] + monomial.coefficients[2]; // 2a2
evaluations[0] = monomial.coefficients[0];
auto prev = evaluations[0];
for (size_t i = 1; i < skip_count + 1; ++i) {
evaluations[i] = 0;
prev += to_add;
to_add += derivative;
}

for (size_t i = skip_count + 1; i < domain_end - 1; ++i) {
prev += to_add;
evaluations[i] = prev;
to_add += derivative;
}
prev += to_add;
evaluations[domain_end - 1] = prev;
}

/**
* @brief Convert from a version with skipped evaluations to one without skipping (with zeroes in previously skipped
* locations)
Expand Down Expand Up @@ -104,15 +159,12 @@ template <class Fr, size_t domain_end, size_t domain_start = 0, size_t skip_coun
// Check if the univariate is identically zero
bool is_zero() const
{
if (!evaluations[0].is_zero()) {
return false;
}
for (size_t i = skip_count + 1; i < LENGTH; ++i) {
if (!evaluations[i].is_zero()) {
return false;
}
}
return true;
return evaluations[0].is_zero();
}

// Write the Univariate evaluations to a buffer
Expand Down Expand Up @@ -350,6 +402,13 @@ template <class Fr, size_t domain_end, size_t domain_start = 0, size_t skip_coun
return os;
}

template <size_t EXTENDED_DOMAIN_END, size_t NUM_SKIPPED_INDICES = 0>
explicit operator Univariate<Fr, EXTENDED_DOMAIN_END, 0, NUM_SKIPPED_INDICES>()
requires(domain_start == 0 && domain_end == 2)
{
return extend_to<EXTENDED_DOMAIN_END, NUM_SKIPPED_INDICES>();
}

/**
* @brief Given a univariate f represented by {f(domain_start), ..., f(domain_end - 1)}, compute the
* evaluations {f(domain_end),..., f(extended_domain_end -1)} and return the Univariate represented by
Expand Down Expand Up @@ -576,15 +635,42 @@ template <class Fr, size_t domain_end, size_t domain_start = 0, size_t skip_coun
public:
static constexpr size_t LENGTH = domain_end - domain_start;
std::span<const Fr, LENGTH> evaluations;
static constexpr size_t MONOMIAL_LENGTH = LENGTH > 1 ? 2 : 1;
using CoefficientAccumulator = UnivariateCoefficientBasis<Fr, MONOMIAL_LENGTH, true>;

UnivariateView() = default;

bool operator==(const UnivariateView& other) const
{
bool r = true;
r = r && (evaluations[0] == other.evaluations[0]);
// a view might have nonzero terms in its skip_count if accessing an original monomial
for (size_t i = skip_count + 1; i < LENGTH; ++i) {
r = r && (evaluations[i] == other.evaluations[i]);
}
return r;
};

const Fr& value_at(size_t i) const { return evaluations[i]; };

template <size_t full_domain_end, size_t full_domain_start = 0>
explicit UnivariateView(const Univariate<Fr, full_domain_end, full_domain_start, skip_count>& univariate_in)
: evaluations(std::span<const Fr>(univariate_in.evaluations.data(), LENGTH)){};

explicit operator UnivariateCoefficientBasis<Fr, 2, true>() const
requires(LENGTH > 1)
{
static_assert(domain_end >= 2);
static_assert(domain_start == 0);

UnivariateCoefficientBasis<Fr, 2, true> result;

result.coefficients[0] = evaluations[0];
result.coefficients[1] = evaluations[1] - evaluations[0];
result.coefficients[2] = evaluations[1];
return result;
}

Univariate<Fr, domain_end, domain_start, skip_count> operator+(const UnivariateView& other) const
{
Univariate<Fr, domain_end, domain_start, skip_count> res(*this);
Expand Down
Loading

0 comments on commit 15475f4

Please sign in to comment.