-
Notifications
You must be signed in to change notification settings - Fork 101
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
Support degree ranges #1667
Support degree ranges #1667
Conversation
One choice we have to make is how to access the degree in the backends: currently we receive it from the pil, but with this change the pil has degree ranges. Two options:
The latter seems cleaner so that a single pil can support different degrees without having to be modified, but also seems like a bigger change now. Edit: this is actually fine: the composite backend overwrites the degree ranges with the specific degree each backend operates over. |
Builds on #1687 Fixed #1572 With this PR, we are using dynamic VADCOP in the RISC-V zk-VM. There were a few smaller fixes needed to make this work. In summary, the changes are as follows: - We set the degree the main machine to `None`, and all fixed lookup machines to the appropriate size. As a consequence, the CPU, all block machines & memory have a dynamic size. - As a consequence, I had to adjust some tests (set the size of all machines, so they can still be run with monolithic provers) *and* was able to remove the `Memory_<size>` machines 🎉 - With the main machine being of flexible size, the prover can chose for how long to run it. We run it for `1 << (MAX_DEGREE_LOG - 2)` steps and compute the bootloader inputs accordingly. With this choice, we can guarantee that the register memory (which can be up to 4x larger than the main machine) does not run out of rows. Note that while we do access `MAX_DEGREE_LOG` in a bunch of places now, this will go away once #1667 is merged, which will allow us to configure the degree range in ASM and for each machine individually. ### Example: ```bash export MAX_LOG_DEGREE=18 cargo run -r --bin powdr-rs compile riscv/tests/riscv_data/many_chunks -o output --continuations cargo run -r --bin powdr-rs execute output/many_chunks.asm -o output --continuations -w cargo run -r --features plonky3,halo2 prove output/many_chunks.asm -d output/chunk_0 --field gl --backend plonky3-composite ``` This leads to the following output: ``` == Proving machine: main (size 65536), stage 0 ==> Proof stage computed in 1.918317417s == Proving machine: main__rom (size 8192), stage 0 ==> Proof stage computed in 45.847375ms == Proving machine: main_binary (size 1024), stage 0 ==> Proof stage computed in 27.718416ms == Proving machine: main_bit2 (size 4), stage 0 ==> Proof stage computed in 15.280667ms == Proving machine: main_bit6 (size 64), stage 0 ==> Proof stage computed in 17.449875ms == Proving machine: main_bit7 (size 128), stage 0 ==> Proof stage computed in 20.717834ms == Proving machine: main_bootloader_inputs (size 262144), stage 0 ==> Proof stage computed in 524.013375ms == Proving machine: main_byte (size 256), stage 0 ==> Proof stage computed in 17.280167ms == Proving machine: main_byte2 (size 65536), stage 0 ==> Proof stage computed in 164.709625ms == Proving machine: main_byte_binary (size 262144), stage 0 ==> Proof stage computed in 504.743917ms == Proving machine: main_byte_compare (size 65536), stage 0 ==> Proof stage computed in 169.881542ms == Proving machine: main_byte_shift (size 65536), stage 0 ==> Proof stage computed in 146.235916ms == Proving machine: main_memory (size 32768), stage 0 ==> Proof stage computed in 326.522167ms == Proving machine: main_poseidon_gl (size 16384), stage 0 ==> Proof stage computed in 1.324662625s == Proving machine: main_regs (size 262144), stage 0 ==> Proof stage computed in 2.009408667s == Proving machine: main_shift (size 32), stage 0 ==> Proof stage computed in 13.71825ms == Proving machine: main_split_gl (size 16384), stage 0 ==> Proof stage computed in 108.019334ms Proof generation took 7.364567s Proof size: 8432928 bytes Writing output/chunk_0/many_chunks_proof.bin. ``` Note that `main_bootloader_inputs` is still equal to the maximum size, we should fix that in a following PR!
Small one extracted from #1667
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.
Cool! LGTM, just a few small comments.
test_data/asm/connect_no_witgen.asm
Outdated
@@ -17,7 +17,7 @@ machine Empty with degree: 16 { | |||
let f: col = |i| i / 2; | |||
w = f; | |||
|
|||
std::check::assert(std::prover::degree() == 2**4, || "Degree is not 2**4"); | |||
std::check::assert(std::prover::min_degree() == 2**4, || "Degree is not 2**4"); |
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.
Why this change? I don't think this would be sound for any larger degree?
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 I agree!
@@ -16,7 +16,7 @@ machine ByteShift with | |||
|
|||
let bit_counts = [256, 32, 4, 2]; | |||
let min_degree = std::array::product(bit_counts); | |||
std::check::assert(std::prover::degree() >= std::array::product(bit_counts), || "The shift machine needs at least 65536 rows to work."); | |||
std::check::assert(std::prover::min_degree() >= std::array::product(bit_counts), || "The shift machine needs at least 65536 rows to work."); |
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.
While that is correct, I think it's actually a feature if this fails because the degree is set to a range! Wouldn't make any sense.
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.
In the composite case, I agree with you: why would you make this trace larger than the minimum? Then we can even remove & 0xffffffff
in P_C
!
But in the monolithic case, we need to be able to fit an instance of this machine inside a trace of arbitrary size, which is where using min_degree
and & 0xffffffff
makes sense.
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.
Right! Forgot about the monolithic case.
@@ -15,7 +15,7 @@ machine ByteBinary with | |||
|
|||
let bit_counts = [256, 256, 3]; | |||
let min_degree = std::array::product(bit_counts); | |||
std::check::assert(std::prover::degree() >= std::array::product(bit_counts), || "The binary machine needs at least 196608 rows to work."); | |||
std::check::assert(std::prover::min_degree() >= std::array::product(bit_counts), || "The binary machine needs at least 196608 rows to work."); |
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.
Same comment as in the shift machine.
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.
same, with inputs
being defined with cross_product
which internally cycles through the values
riscv/src/continuations.rs
Outdated
@@ -113,6 +105,20 @@ where | |||
} else { | |||
pipeline | |||
}; | |||
|
|||
// get the length of the columns in the machine we want to extend with external witness values |
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.
// get the length of the columns in the machine we want to extend with external witness values | |
// get the length of the main machine |
riscv/src/continuations.rs
Outdated
{ | ||
n.value.clone().try_into().unwrap() | ||
} else { | ||
unimplemented!("Continuations rely on `Main` defining a max_degree in asm") |
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.
Would that be the problem that it's not set at all? I think it's more that it needs to be a number as opposed to a complex expression (like 2**20
, which would actually make sense?).
I'm wondering if we can re-use the code from above and just request the PIL? Anyway, I think if we leave it like this, the error message could be more like it was before #1683, see this diff.
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 it's not set at all, it defaults to 1 << MAX_DEGREE
which is too large for the memory machine not to run out of rows. If we run this check at the pil level, then to do it properly we'd need to get the main degree, get the memory degree, and check that it's 4x. Is this what you're suggesting?
What we should to go towards is removing these defaults and move all this logic to asm, where the degree ranges would get explicitly set on the different traces so we don't need to check anything here.
riscv/src/code_gen.rs
Outdated
1 << (powdr_linker::MIN_DEGREE_LOG - 2), | ||
1 << (*powdr_linker::MAX_DEGREE_LOG - 2), |
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.
1 << (powdr_linker::MIN_DEGREE_LOG - 2), | |
1 << (*powdr_linker::MAX_DEGREE_LOG - 2), | |
1 << powdr_linker::MIN_DEGREE_LOG, | |
// We expect some machines (e.g. register memory) to use up to 4x the number | |
// of rows as main. By setting the max degree of main to be smaller by a factor | |
// of 4, we ensure that we don't run out of rows in those machines. | |
1 << (*powdr_linker::MAX_DEGREE_LOG - 2), |
I don't really see a reason to set a lower min degree! That will just add commitments to tiny fixed columns to the verification key. In practice, for any computation of significant size, we'd use the max size anyway, except for the last chunk. I think we could even set it to a larger value, like 10.
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.
There is a bad reason: if we set min and max to the same value, the linker assumes that we are in the monolithic case and sets all degrees to that same value. It's the case mentioned here which we cannot support. What we could do here is reduce the range log to (MAX_RANGE - 3)..(MAX_RANGE - 2)
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.
Oh, but I didn't suggest setting it to the same value. Not quite sure about the tradeoffs, but I think it could be a useful feature that we can prove the last chunk (or only chunk in case of a small computation) faster because it is smaller. But going below 1 << MIN_DEGREE_LOG
(=32 in practice) seems unnecessary, proving 32 rows should take no time.
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.
Oh I misread your suggestion! Yes I agree.
if degree.min == degree.max { | ||
Ok(Value::Integer(degree.min.into()).into()) | ||
} else { | ||
Err(EvalError::DataNotAvailable) |
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.
Is that the right error?
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.
You can argue that it is, as the degree will eventually be known (in constant evaluation), it's just not known yet at this stage where it's still a range. We could also make it always unavailable at this stage, then that means we cannot access degree
outside of constant definitions.
@@ -407,7 +446,7 @@ namespace main_sub(16); | |||
pc' = (1 - first_step') * pc_update; | |||
pol commit _output_0_free_value; | |||
1 $ [0, pc, instr__jump_to_operation, instr__reset, instr__loop, instr_return, _output_0_const, _output_0_read_free, read__output_0_pc, read__output_0__input_0] in main_sub__rom.latch $ [main_sub__rom.operation_id, main_sub__rom.p_line, main_sub__rom.p_instr__jump_to_operation, main_sub__rom.p_instr__reset, main_sub__rom.p_instr__loop, main_sub__rom.p_instr_return, main_sub__rom.p__output_0_const, main_sub__rom.p__output_0_read_free, main_sub__rom.p_read__output_0_pc, main_sub__rom.p_read__output_0__input_0]; | |||
namespace main_sub__rom(8); | |||
namespace main_sub__rom(16); |
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.
Why did this change?
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.
Main having a single degree triggers the monolithic mode. To be fixed properly with explicit proof objects soon.
.degree | ||
.clone() | ||
.unwrap_or_else(|| Expression::from(self.code_lines.len().next_power_of_two() as u32)); | ||
// this can lead to false negatives as we apply expression equality here, so `4` and `2 + 2` would be considered different. |
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.
Where do we check for expression equality?
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.
Oh, this looks like a leftover comment of something that got moved somewhere else
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.
Actually no, we check expression equality in is_static
riscv/src/continuations.rs
Outdated
|
||
let length: usize = match max_degree_expr { | ||
Some(Expression::Number(_, n)) => n.value.clone().try_into().unwrap(), | ||
// if the max degree is not defined, it defaults to |
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.
Is this complete?
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.
oops
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.
fixed
conflicts |
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.
Let's gooo 🚀
We currently hardcode the range of degrees that variable degree machines are preprocessed for. Expose that in machines instead.
This changes pil namespaces to accept a min and max degree:
It adds two new builtins:
And sets the behavior of the
std::prover::degree
builtin to only succeed ifmin_degree
andmax_degree
are equal.