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

Support degree ranges #1667

Merged
merged 33 commits into from
Sep 3, 2024
Merged

Support degree ranges #1667

merged 33 commits into from
Sep 3, 2024

Conversation

Schaeff
Copy link
Collaborator

@Schaeff Schaeff commented Aug 8, 2024

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:

namespace main(123..456);
namespace main(5); // allowed for backward compatibility, translates to `5..5`

It adds two new builtins:

std::prover::min_degree
std::prover::max_degree

And sets the behavior of the std::prover::degree builtin to only succeed if min_degree and max_degree are equal.

github-merge-queue bot pushed a commit that referenced this pull request Aug 12, 2024
Pulled out of #1676, where it helps making the tests run faster.

I think this could possibly be removed by #1667 again.
@Schaeff
Copy link
Collaborator Author

Schaeff commented Aug 12, 2024

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:

  • when we modify the pil in the composite backend, replace the degree range with the single degree for that proof so we can access it like before
  • get the degree from somewhere else, such as the fixed columns or the verification key

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.

backend/src/composite/mod.rs Outdated Show resolved Hide resolved
github-merge-queue bot pushed a commit that referenced this pull request Aug 15, 2024
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!
topanisto pushed a commit that referenced this pull request Aug 15, 2024
Pulled out of #1676, where it helps making the tests run faster.

I think this could possibly be removed by #1667 again.
github-merge-queue bot pushed a commit that referenced this pull request Aug 15, 2024
@Schaeff Schaeff marked this pull request as ready for review August 18, 2024 20:23
Copy link
Collaborator

@georgwiese georgwiese left a 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.

@@ -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");
Copy link
Collaborator

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?

Copy link
Collaborator Author

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.");
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator

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.");
Copy link
Collaborator

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.

Copy link
Collaborator Author

@Schaeff Schaeff Aug 22, 2024

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

@@ -113,6 +105,20 @@ where
} else {
pipeline
};

// get the length of the columns in the machine we want to extend with external witness values
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// get the length of the columns in the machine we want to extend with external witness values
// get the length of the main machine

{
n.value.clone().try_into().unwrap()
} else {
unimplemented!("Continuations rely on `Main` defining a max_degree in asm")
Copy link
Collaborator

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.

Copy link
Collaborator Author

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.

Comment on lines 318 to 319
1 << (powdr_linker::MIN_DEGREE_LOG - 2),
1 << (*powdr_linker::MAX_DEGREE_LOG - 2),
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
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.

Copy link
Collaborator Author

@Schaeff Schaeff Aug 21, 2024

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)

Copy link
Collaborator

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.

Copy link
Collaborator Author

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)
Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

linker/src/lib.rs Show resolved Hide resolved
@@ -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);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why did this change?

Copy link
Collaborator Author

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.
Copy link
Collaborator

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?

Copy link
Collaborator Author

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

Copy link
Collaborator Author

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


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
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this complete?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oops

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fixed

@leonardoalt
Copy link
Member

conflicts

Copy link
Collaborator

@georgwiese georgwiese left a comment

Choose a reason for hiding this comment

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

Let's gooo 🚀

@Schaeff Schaeff added this pull request to the merge queue Sep 3, 2024
Merged via the queue into main with commit 7f259b4 Sep 3, 2024
14 checks passed
@Schaeff Schaeff deleted the degree-range branch September 3, 2024 13:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants