-
Notifications
You must be signed in to change notification settings - Fork 180
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
Add support for periodic columns in LogUp-GKR #307
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! Looks good! I left some comments inline - the main one is about assuming that periodic oracles being always at the end of the list of oracles. But not sure if that's a big issue.
sumcheck/src/verifier/mod.rs
Outdated
let mut query = proof.0.openings_claim.openings.clone(); | ||
query.extend_from_slice(&periodic_columns_evaluations); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here we are assuming that periodic column values are always at the end of the query, right? But when we define oracles, I don't think this is enforced. Could this cause issues?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed we are, and this will break things if that is not the case. Should we add checks and panic throughout to enforce this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we always expect it to be at the end of the query
buffer, can't we simply not pass it to build_query()
, and append it ourselves?
IIUC, there is no other correct way to do it, so there's no point in asking (and hoping for) the user to do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I think we should try to derive oracles for periodic columns so that the user does not need to make sure that oracles are ordered correctly.
Also, maybe we define query something like this:
pub struct Query<E: FieldElement> {
pub committed_values: Vec<E>,
pub periodic_value: Vec<E>,
}
This way, there is no ambiguity about where the periodic values should go (though, it does require 2 vectors instead of 1).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we always expect it to be at the end of the
query
buffer, can't we simply not pass it tobuild_query()
, and append it ourselves?
I am not sure how build_query()
fits into the code snippet above. build_query()
is specifically for building from the main trace segment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. I think we should try to derive oracles for periodic columns so that the user does not need to make sure that oracles are ordered correctly.
I am not sure I understand fully but the way I see solving the issue raised is by separating the the oracles into committed and periodic.
My assumption was that, if we are going to test the consistency between get_oracles()
and build_query()
anyway then we might as well impose that periodic columns are at the end. The test will check for this any and imposing that periodic columns are towards the end is implicit in requiring consistency between get_oracles()
and build_query()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure how build_query() fits into the code snippet above. build_query() is specifically for building from the main trace segment.
We are currently making an assumption about the query
argument to evaluate_query()
- namely that the periodic columns need to be appended at the end of the buffer. Hence, this assumption needs to be upheld by build_query()
as well.
My point was: why give the user the possibly to screw it up and place the periodic columns somewhere other than at the end of the query
buffer? We could instead change build_query()
to be
fn build_query<E>(&self, frame: &EvaluationFrame<E>, query: &mut [E])
where
E: FieldElement<BaseField = Self::BaseField>,
{ ... }
and append periodic values manually after each call to build_query()
, for example here:
evaluator.build_query(&main_frame, &mut query);
query.extend(periodic_values_row);
i.e. we are enforcing that assumption ourselves, and not leaving the user any possibility for mistake.
IIUC, @irakliyk built on this and suggested we make the structure explicit by representing the periodic values separately (instead of appended at the end up the buffer). So the previous example would look like
evaluator.build_query(&main_frame, &mut query);
let query_full = Query::new(query, periodic_values_row);
Does that make sense? Maybe I'm missing something too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, it does make sense. One thing that is different with respect to the new proposal is with respect to new allocations. In the current way, we just write to a buffer but with the current proposal I am not sure if we are incurring some kind of overhead when creating Query
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Query::new()
should be fine here - i.e., it would not allocate anything as it would leave on the stack.
table.push(values) | ||
} | ||
} | ||
PeriodicTable { table } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I would rewrite this as
let table: Vec<Vec<F>> = self
.get_oracles()
.iter()
.filter_map(|oracle| {
if let LogUpGkrOracle::PeriodicValue(values) = oracle {
Some(values.into_iter().copied().map(F::from).collect())
} else {
None
}
})
.collect();
PeriodicTable{ table }
E
is not used, and it is redundant, right? It seems like we could always makeF::BaseField
the basefield, andF
the extension field (orF::BaseField
andF
both be the base field).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion, switched to it
Indeed, the generic is redundant now, it is a leftover from a previous iteration. Removed now
@@ -160,6 +160,7 @@ pub fn sum_check_prove_higher_degree< | |||
r_sum_check: E, | |||
log_up_randomness: Vec<E>, | |||
mut mls: Vec<MultiLinearPoly<E>>, | |||
periodic_table: &mut PeriodicTable<E>, | |||
coin: &mut impl RandomCoin<Hasher = H, BaseField = E::BaseField>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
coin: &mut impl RandomCoin<Hasher = H, BaseField = E::BaseField>, | |
mut periodic_table: PeriodicTable<E>, |
We can pass this to sumcheck_round()
as &mut periodic_table
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
sumcheck/src/verifier/mod.rs
Outdated
let mut query = proof.0.openings_claim.openings.clone(); | ||
query.extend_from_slice(&periodic_columns_evaluations); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we always expect it to be at the end of the query
buffer, can't we simply not pass it to build_query()
, and append it ourselves?
IIUC, there is no other correct way to do it, so there's no point in asking (and hoping for) the user to do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Left a few nits.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall, looks good! Thank you! I do wonder if we should change things slight based on the discussion in #307 (comment). Basically, it seems like adding periodic values to the list of other oracles imposes some implicit assumptions. So, maybe we should just treat them separately (and we kind of do in a few places already).
One way to do this could be as follows:
First, we remove PeriodicValue
variant from the LogUpGkrOracle
enum.
Then, we add a dedicated method to the LogUpGkrEvaluator
to get periodic columns. Something like:
pub trait LogUpGkrEvaluator {
fn get_periodic_column_values(&self) -> Vec<Vec<Self::BaseField>>;
}
Then, we restrict LogUpGkrEvaluator::build_query()
to work only with committed columns. So, it would look something like this:
pub trait LogUpGkrEvaluator {
fn build_query<E>(&self, frame: &EvaluationFrame<E>, query: &mut [E])
where
E: FieldElement<BaseField = Self::BaseField>;
}
Finally, we pass periodic values to the LogUpGkrEvaluator::evaluate_query()
explicitly like so:
pub trait LogUpGkrEvaluator {
fn evaluate_query<F, E>(
&self,
query: &[F],
periodic_values: &[F],
logup_randomness: &[E],
numerators: &mut [E],
denominators: &mut [E],
) where
F: FieldElement<BaseField = Self::BaseField>,
E: FieldElement<BaseField = Self::BaseField> + ExtensionOf<F>;
}
This way, we don't make any implicit assumptions about where periodic values go in the query
- they are always treated separately.
Lastly, the implementation of LogUpGkrEvaluator ::build_periodic_values()
would be simplified as well.
One other question: with the suggestions I made above, it looks like handling of periodic values in I guess the answer to this depends on how much overhead this will introduce as we may end up with some periodic values passed to |
Implemented your suggestion, although the simplifications in some parts come at the expense of less uniformity in the high degree sum-check prover. I will create an issue to optimize the way periodic column (multi-linears) are handled there as I am sure the succinctness of these column can be leveraged in order to optimize the implementation with respect to them. |
That's correct, but maybe when building the periodic table for LogUp-GKR we can return only the values figuring in LogUp-GKR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! Thank you! I left one small comment inline (but we can also merge w/o it and fix it later).
sumcheck/src/prover/high_degree.rs
Outdated
@@ -280,21 +298,28 @@ fn sumcheck_round<E: FieldElement>( | |||
evaluator: &impl LogUpGkrEvaluator<BaseField = <E as FieldElement>::BaseField>, | |||
eq_ml: &MultiLinearPoly<E>, | |||
mls: &[MultiLinearPoly<E>], | |||
periodic_table: &mut PeriodicTable<E>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be &mut PeriodicTable<E>
? Doesn't seem like we are mutating the table in this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! Fixed
No description provided.