From e286da46f7470e0c1a9c354557449151762de156 Mon Sep 17 00:00:00 2001 From: Georg Wiese Date: Mon, 9 Dec 2024 19:12:33 +0100 Subject: [PATCH] Bus: Remove `acc_next` + materialize folded tuple (#2201) This PR: - Removes the `acc_next` columns, which were only needed because of a limitation of prover functions. The prover function that existed is now removed entirely, because we use the hand written witgen anyway, see #2191. - Also this PR materializes the folded tuple. This lowers the degree of the constraints if the tuples being sent have a degree > 1. It also enables next references in the tuple being sent. As a result, we can now generate Plonky3 proofs with a bus! ```bash cargo run -r --features plonky3 --bin powdr-rs compile riscv/tests/riscv_data/keccak -o output --max-degree-log 18 --field gl cargo run -r --features plonky3 pil output/keccak.asm -o output -f --field gl --prove-with plonky3 --linker-mode bus ``` The proof generation takes 8.32s (of which 394ms are spent on generating the second-stage witness). This compares to 2.07s proof time without a bus. --- executor/src/witgen/bus_accumulator/mod.rs | 45 ++++++++-------------- std/protocols/bus.asm | 44 ++++++++++++--------- 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/executor/src/witgen/bus_accumulator/mod.rs b/executor/src/witgen/bus_accumulator/mod.rs index 9479383185..76e1c6f38e 100644 --- a/executor/src/witgen/bus_accumulator/mod.rs +++ b/executor/src/witgen/bus_accumulator/mod.rs @@ -85,15 +85,7 @@ impl<'a, T: FieldElement> BusAccumulatorGenerator<'a, T> { let accumulators = self .bus_interactions .par_iter() - .flat_map(|bus_interaction| { - let (acc1, acc2) = self.interaction_columns(bus_interaction); - let next1 = next(&acc1); - let next2 = next(&acc2); - - // We assume that the second-stage witness columns are in this order, - // for each bus interaction. - [acc1, acc2, next1, next2] - }) + .flat_map(|bus_interaction| self.interaction_columns(bus_interaction)) .collect::>(); self.pil @@ -107,11 +99,13 @@ impl<'a, T: FieldElement> BusAccumulatorGenerator<'a, T> { fn interaction_columns( &self, bus_interaction: &PhantomBusInteractionIdentity, - ) -> (Vec, Vec) { + ) -> Vec> { let intermediate_definitions = self.pil.intermediate_definitions(); let empty_challenges = BTreeMap::new(); let size = self.trace_values.height(); + let mut folded1 = vec![T::zero(); size]; + let mut folded2 = vec![T::zero(); size]; let mut acc1 = vec![T::zero(); size]; let mut acc2 = vec![T::zero(); size]; @@ -128,26 +122,26 @@ impl<'a, T: FieldElement> BusAccumulatorGenerator<'a, T> { }; let multiplicity = evaluator.evaluate(&bus_interaction.multiplicity); + let tuple = bus_interaction + .tuple + .0 + .iter() + .map(|r| evaluator.evaluate(r)) + .collect::>(); + let folded = self.beta - self.fingerprint(&tuple); + let new_acc = match multiplicity.is_zero() { true => current_acc, - false => { - let tuple = bus_interaction - .tuple - .0 - .iter() - .map(|r| evaluator.evaluate(r)) - .collect::>(); - - let fingerprint = self.beta - self.fingerprint(&tuple); - current_acc + fingerprint.inverse() * multiplicity - } + false => current_acc + folded.inverse() * multiplicity, }; + folded1[i] = folded.0; + folded2[i] = folded.1; acc1[i] = new_acc.0; acc2[i] = new_acc.1; } - (acc1, acc2) + vec![folded1, folded2, acc1, acc2] } /// Fingerprints a tuples of field elements, using the pre-computed powers of alpha. @@ -170,10 +164,3 @@ fn powers_of_alpha(alpha: Fp2, n: usize) -> Vec> { }) .collect::>() } - -/// Rotates a column to the left. -fn next(column: &[T]) -> Vec { - let mut result = column.to_vec(); - result.rotate_left(1); - result -} diff --git a/std/protocols/bus.asm b/std/protocols/bus.asm index 0bf93d8ce7..66fdaab295 100644 --- a/std/protocols/bus.asm +++ b/std/protocols/bus.asm @@ -16,6 +16,9 @@ use std::protocols::fingerprint::fingerprint_with_id; use std::protocols::fingerprint::fingerprint_with_id_inter; use std::math::fp2::required_extension_size; use std::prover::eval; +use std::field::known_field; +use std::field::KnownField; +use std::check::panic; /// Sends the tuple (id, tuple...) to the bus by adding /// `multiplicity / (beta - fingerprint(id, tuple...))` to `acc` @@ -40,7 +43,29 @@ let bus_interaction: expr, expr[], expr -> () = constr |id, tuple, multiplicity| let beta = fp2_from_array(array::new(required_extension_size(), |i| challenge(0, i + 3))); // Implemented as: folded = (beta - fingerprint(id, tuple...)); - let folded = sub_ext(beta, fingerprint_with_id_inter(id, tuple, alpha)); + let folded = match known_field() { + Option::Some(KnownField::Goldilocks) => { + // Materialized as a witness column for two reasons: + // - It makes sure the constraint degree is independent of the input tuple. + // - We can access folded', even if the tuple contains next references. + // Note that if all expressions are degree-1 and there is no next reference, + // this is wasteful, but we can't check that here. + let folded = fp2_from_array( + array::new(required_extension_size(), + |i| std::prover::new_witness_col_at_stage("folded", 1)) + ); + constrain_eq_ext(folded, sub_ext(beta, fingerprint_with_id_inter(id, tuple, alpha))); + folded + }, + // The case above triggers our hand-written witness generation, but on Bn254, we'd not be + // on the extension field and use the automatic witness generation. + // However, it does not work with a materialized folded tuple. At the same time, Halo2 + // (the only prover that supports BN254) does not have a hard degree bound. So, we can + // in-line the expression here. + Option::Some(KnownField::BN254) => sub_ext(beta, fingerprint_with_id_inter(id, tuple, alpha)), + _ => panic("Unexpected field!") + }; + let folded_next = next_ext(folded); let m_ext = from_base(multiplicity); @@ -62,23 +87,6 @@ let bus_interaction: expr, expr[], expr -> () = constr |id, tuple, multiplicity| ); constrain_eq_ext(update_expr, from_base(0)); - - // In the extension field, we need a prover function for the accumulator. - if needs_extension() { - // TODO: Helper columns, because we can't access the previous row in hints - let acc_next_col = std::array::map(acc, |_| std::prover::new_witness_col_at_stage("acc_next", 1)); - query |i| { - let _ = std::array::zip( - acc_next_col, - compute_next_z(is_first, id, tuple, multiplicity, acc_ext, alpha, beta), - |acc_next, hint_val| std::prover::provide_value(acc_next, i, hint_val) - ); - }; - std::array::zip(acc, acc_next_col, |acc_col, acc_next| { - acc_col' = acc_next - }); - } else { - } }; /// Compute acc' = acc * (1 - is_first') + multiplicity' / fingerprint(id, tuple...),