zcash / halo2

The Halo2 zero-knowledge proving system
https://zcash.github.io/halo2/
Other
718 stars 488 forks source link

Define a stable serialization format for `halo2::plonk::ProvingKey` #443

Open HAOYUatHZ opened 2 years ago

HAOYUatHZ commented 2 years ago

We only have for read & write for halo2::plonk::VerifyingKey but not for halo2::plonk::ProvingKey.

Doesn't it make sense to store it to and restore it from a file?

daira commented 2 years ago

Because Halo 2 doesn't have a trusted setup (and the generators are computed by deterministically hashing to the curve), the only thing necessary to reproduce the proving key is the code of the circuit. Since that's necessary for proving anyway, I don't think there's an advantage to reading and writing proving keys from/to disk. The same argument would not apply to verification: you can verify a proof with just the verifying key, without needing the code for the circuit.

str4d commented 2 years ago

The same argument would not apply to verification: you can verify a proof with just the verifying key, without needing the code for the circuit.

That's not quite true: the verification key depends on the circuit configuration (which defines e.g. the number of columns). Note that the VerifyingKey::read function takes the circuit as a generic parameter.

We could in theory rework the Circuit trait to split apart configuration and synthesis, but that would be pretty invasive, and every gadget a circuit uses would need to be similarly split if you wanted to not include synthesis code. Instead, I think it's more likely that we'd add a type that developers can use to hard-code a circuit configuration (maybe as part of optimisations to the floor planning), and then that type itself can just be what is used in verification-only contexts.

HAOYUatHZ commented 2 years ago

the only thing necessary to reproduce the proving key is the code of the circuit. Since that's necessary for proving anyway, I don't think there's an advantage to reading and writing proving keys from/to disk.

I think this only considers the scenario a prover keeps running.

If we save the generated proving key, when we restart a prover, we can reuse the proving key to prove for different witnesses.

str4d commented 2 years ago

If we save the generated proving key, when we restart a prover, we can reuse the proving key to prove for different witnesses.

In proving systems like Groth16, it was necessary to read the proving key from disk because there was information stored within it that we couldn't derive on-the-fly. For Halo 2, reading the proving key from disk would solely be a potential performance optimisation, if it takes less time to read the proving key from disk than to re-compute it.

I recently added key generation benchmarks to benches/plonk.rs, and for its particular circuit structure, I see the following times on a Ryzen 9 5950X:

k Average keygen time (s)
8 0.13
9 0.24
10 0.31
11 0.60
12 1.21
13 2.42
14 4.87
15 9.83
16 19.86

So for circuits even a few sizes bigger than the Orchard circuit (which uses k = 11), generating the keys on first start is fast enough that it's much simpler to do so than deal with the complexity of storing parameters on disk (and needing to find and load them on restart). The keygen time roughly doubles as k increases, so it definitely gets noticeably large for very large circuits, but if the prover process is long-lived the amortized cost might still be low enough to not pay the on-disk complexity. But I can imagine that for very large circuits used by short-lived prover processes (is this a use case people have?) reading from disk could be faster. We don't have benchmarks of what the sizes of the proving keys would be on disk though, so we don't actually know what the other half of the equation looks like yet.

@HAOYUatHZ do you have specific performance numbers for computing proving keys for your circuit sizes / compositions, vs parsing them?

HAOYUatHZ commented 2 years ago

We are working on https://github.com/appliedzkp/zkevm-circuits. We haven't had a performance number yet, but the k used in production I assume will be >=20. (The k for "evm_circuit" we are using is 11 for now, and soon to be 16 after some queuing PRs being merged. And such k is only for "evm_circuit". We also have other circuits. My estimate is >=20 in future production.)

But this is not urgent anyway.

In fact, we are facing another problem: in https://github.com/appliedzkp/zkevm-circuits/blob/main/circuit-benchmarks/src/evm_circuit.rs#L84 using the vk in memory works fine, but saving&reloading the VK (not PK mentioned in this issue) doesn't work. (and https://github.com/zcash/halo2/blob/main/examples/sha256/benches.rs works good) Would you happen to have a chance to know any clue?

str4d commented 2 years ago

@daira pointed out that:

very large circuits used by short-lived prover processes (is this a use case people have?)

is the exact use case for a CI workflow of a very large circuit. So we will likely need to figure out some serialization workflow, but it probably won't need to be used much in production I suspect.

HAOYUatHZ commented 2 years ago

@daira pointed out that:

very large circuits used by short-lived prover processes (is this a use case people have?)

is the exact use case for a CI workflow of a very large circuit. So we will likely need to figure out some serialization workflow, but it probably won't need to be used much in production I suspect.

IHMO k=20 corresponds to 5 min (according to the numbers you listed above), which is still a considerable time.

str4d commented 2 years ago

Yes, but this time will be reduced by improved compute (and e.g. GPU support), which applications using these large circuits will favour anyway for proving. So if the application is going to be running for at least several weeks to a month, then a few minutes delay at startup (where the application is doing the equivalent work of creating a few proofs) is likely a fine trade-off.

HAOYUatHZ commented 2 years ago

I see.

str4d commented 2 years ago

Sorry, I didn't mean to imply this issue should be closed. We still will need to address this for at a minimum CI purposes, and whatever solution is arrived at for that should also be made to work for production.

HAOYUatHZ commented 2 years ago

great to know! thanks!

narodnik commented 2 years ago

I'm getting these benchmarks:

  params created [10.670869912 s]
  vk created [5.112461805 s]
  pk created [6.752458356 s]

When program loads a large number of circuits, this will cause significant slowdown if we cannot save the proving key.

str4d commented 2 years ago

VerifyingKey::{read, write} were removed, as we still need to define a stable serialization format for viewing keys (#449). I'm repurposing this issue to be the same task for proving keys.

narodnik commented 2 years ago

On an old laptop, just for the ProvingKey, I'm getting this speed (k = 11):

PK: [10.826833632s]
VK: [31.91576229s]
nagatoism commented 1 year ago

Any progress on this? There scenarios where you would like to send the prover key to another process. So I do have need on this feature.