diff --git a/stwo_cairo_prover/crates/prover/src/cairo_air/debug.rs b/stwo_cairo_prover/crates/prover/src/cairo_air/debug.rs index 8e1533ab..a3fb93bf 100644 --- a/stwo_cairo_prover/crates/prover/src/cairo_air/debug.rs +++ b/stwo_cairo_prover/crates/prover/src/cairo_air/debug.rs @@ -477,7 +477,7 @@ pub fn track_cairo_relations( RelationTrackerComponent::new( tree_span_provider, memory_address_to_id::Eval { - log_n_rows: claim.memory_address_to_id.log_size, + log_size: claim.memory_address_to_id.log_size, lookup_elements: relations::MemoryAddressToId::dummy(), }, 1 << claim.memory_address_to_id.log_size, diff --git a/stwo_cairo_prover/crates/prover/src/components/memory/memory_address_to_id/component.rs b/stwo_cairo_prover/crates/prover/src/components/memory/memory_address_to_id/component.rs index 45c29c4e..a8bb337d 100644 --- a/stwo_cairo_prover/crates/prover/src/components/memory/memory_address_to_id/component.rs +++ b/stwo_cairo_prover/crates/prover/src/components/memory/memory_address_to_id/component.rs @@ -3,30 +3,43 @@ use stwo_prover::constraint_framework::{ EvalAtRow, FrameworkComponent, FrameworkEval, RelationEntry, }; use stwo_prover::core::channel::Channel; +use stwo_prover::core::fields::m31::M31; use stwo_prover::core::fields::qm31::SecureField; use stwo_prover::core::fields::secure_column::SECURE_EXTENSION_DEGREE; use stwo_prover::core::pcs::TreeVec; use crate::relations; -pub const N_ADDR_TO_ID_COLUMNS: usize = 3; +// TODO(Ohad): Address should be a preprocessed `seq`. +pub const N_ADDR_COLUMNS: usize = 1; + +// Split the (ID , Multiplicity) columns to shorter chunks. This is done to improve the performance +// during The merkle commitment and FRI, as this component is usually the tallest in the Cairo AIR. +// TODO(Ohad): Change split to 8 after seq is implemented. NOTE: it is possible to split further +// with an expansion trick similar to the one used in XOR. Investigate if it is worth it. +pub(super) const LOG_SPLIT_SIZE: u32 = 2; +pub(super) const SPLIT_SIZE: usize = 1 << LOG_SPLIT_SIZE; +pub(super) const N_ID_AND_MULT_COLUMNS_PER_CHUNK: usize = 2; +pub(super) const N_TRACE_COLUMNS: usize = + N_ADDR_COLUMNS + SPLIT_SIZE * N_ID_AND_MULT_COLUMNS_PER_CHUNK; + pub type Component = FrameworkComponent; -// TODO(ShaharS): Break to repititions in order to batch the logup. #[derive(Clone)] pub struct Eval { - pub log_n_rows: u32, + // The log size of the component after split. + pub log_size: u32, pub lookup_elements: relations::MemoryAddressToId, } impl Eval { // TODO(ShaharS): use Seq column for address, and also use repititions. pub const fn n_columns(&self) -> usize { - N_ADDR_TO_ID_COLUMNS + N_TRACE_COLUMNS } pub fn new(claim: Claim, lookup_elements: relations::MemoryAddressToId) -> Self { Self { - log_n_rows: claim.log_size, + log_size: claim.log_size, lookup_elements, } } @@ -34,7 +47,7 @@ impl Eval { impl FrameworkEval for Eval { fn log_size(&self) -> u32 { - self.log_n_rows + self.log_size } fn max_constraint_log_degree_bound(&self) -> u32 { @@ -42,13 +55,17 @@ impl FrameworkEval for Eval { } fn evaluate(&self, mut eval: E) -> E { - let address_and_id: [E::F; 2] = std::array::from_fn(|_| eval.next_trace_mask()); - let multiplicity = eval.next_trace_mask(); - eval.add_to_relation(&[RelationEntry::new( - &self.lookup_elements, - E::EF::from(-multiplicity), - &address_and_id, - )]); + let address = eval.next_trace_mask(); + for i in 0..SPLIT_SIZE { + let id = eval.next_trace_mask(); + let multiplicity = eval.next_trace_mask(); + let address = address.clone() + E::F::from(M31((i * (1 << self.log_size())) as u32)); + eval.add_to_relation(&[RelationEntry::new( + &self.lookup_elements, + E::EF::from(-multiplicity), + &[address, id], + )]); + } eval.finalize_logup(); eval @@ -62,8 +79,8 @@ pub struct Claim { impl Claim { pub fn log_sizes(&self) -> TreeVec> { let preprocessed_log_sizes = vec![self.log_size]; - let trace_log_sizes = vec![self.log_size; N_ADDR_TO_ID_COLUMNS]; - let interaction_log_sizes = vec![self.log_size; SECURE_EXTENSION_DEGREE]; + let trace_log_sizes = vec![self.log_size; N_TRACE_COLUMNS]; + let interaction_log_sizes = vec![self.log_size; SECURE_EXTENSION_DEGREE * SPLIT_SIZE]; TreeVec::new(vec![ preprocessed_log_sizes, trace_log_sizes, diff --git a/stwo_cairo_prover/crates/prover/src/components/memory/memory_address_to_id/prover.rs b/stwo_cairo_prover/crates/prover/src/components/memory/memory_address_to_id/prover.rs index 8e3c714f..1faa3619 100644 --- a/stwo_cairo_prover/crates/prover/src/components/memory/memory_address_to_id/prover.rs +++ b/stwo_cairo_prover/crates/prover/src/components/memory/memory_address_to_id/prover.rs @@ -1,9 +1,10 @@ -use itertools::Itertools; +use std::iter::zip; +use std::simd::Simd; + +use itertools::{izip, Itertools}; use stwo_prover::constraint_framework::logup::LogupTraceGenerator; use stwo_prover::constraint_framework::Relation; -use stwo_prover::core::backend::simd::column::BaseColumn; use stwo_prover::core::backend::simd::m31::{PackedBaseField, PackedM31, LOG_N_LANES, N_LANES}; -use stwo_prover::core::backend::simd::qm31::PackedSecureField; use stwo_prover::core::backend::simd::SimdBackend; use stwo_prover::core::backend::{Col, Column}; use stwo_prover::core::fields::m31::{BaseField, M31}; @@ -12,8 +13,10 @@ use stwo_prover::core::poly::circle::{CanonicCoset, CircleEvaluation}; use stwo_prover::core::poly::BitReversedOrder; use stwo_prover::core::vcs::blake2_merkle::Blake2sMerkleChannel; -use super::component::{Claim, InteractionClaim, N_ADDR_TO_ID_COLUMNS}; -use crate::components::memory::MEMORY_ADDRESS_BOUND; +use super::component::{Claim, InteractionClaim, SPLIT_SIZE}; +use crate::components::memory_address_to_id::component::{ + N_ID_AND_MULT_COLUMNS_PER_CHUNK, N_TRACE_COLUMNS, +}; use crate::input::mem::Memory; use crate::relations; @@ -26,14 +29,11 @@ pub struct ClaimGenerator { } impl ClaimGenerator { pub fn new(mem: &Memory) -> Self { - let mut ids = (0..mem.address_to_id.len()) + let ids = (0..mem.address_to_id.len()) .map(|addr| mem.get_raw_id(addr as u32)) .collect_vec(); - let size = ids.len().next_power_of_two(); - assert!(size <= MEMORY_ADDRESS_BOUND); - ids.resize(size, 0); - let multiplicities = vec![0; size]; + let multiplicities = vec![0; ids.len()]; Self { ids, multiplicities, @@ -69,50 +69,54 @@ impl ClaimGenerator { } pub fn write_trace( - self, + mut self, tree_builder: &mut TreeBuilder<'_, '_, SimdBackend, Blake2sMerkleChannel>, ) -> (Claim, InteractionClaimGenerator) { - let size = self.ids.len(); - let mut trace = (0..N_ADDR_TO_ID_COLUMNS) - .map(|_| Col::::zeros(size)) - .collect_vec(); + let size = (self.ids.len() / SPLIT_SIZE).next_power_of_two(); + let packed_size = size.div_ceil(N_LANES); + let mut trace: [_; N_TRACE_COLUMNS] = + std::array::from_fn(|_| Col::::zeros(size)); - let inc = PackedBaseField::from_array(std::array::from_fn(|i| { - BaseField::from_u32_unchecked((i) as u32) - })); + // Pad to a multiple of `N_LANES`. + let next_multiple_of_16 = ((self.ids.len() + 15) >> 4) << 4; + self.ids.resize(next_multiple_of_16, 0); + self.multiplicities.resize(next_multiple_of_16, 0); // TODO(Ohad): avoid copy. - let packed_ids = self + let id_it = self .ids .array_chunks::() - .map(|chunk| { - PackedM31::from_array(std::array::from_fn(|i| M31::from_u32_unchecked(chunk[i]))) - }) - .collect_vec(); + .map(|&chunk| unsafe { PackedM31::from_simd_unchecked(Simd::from_array(chunk)) }); + let multiplicities_it = self + .multiplicities + .array_chunks::() + .map(|&chunk| unsafe { PackedM31::from_simd_unchecked(Simd::from_array(chunk)) }); - // Replace with seq. - for (i, id) in packed_ids.iter().enumerate() { + let inc = + PackedM31::from_array(std::array::from_fn(|i| M31::from_u32_unchecked((i) as u32))); + for i in 0..packed_size { trace[0].data[i] = - PackedM31::broadcast(BaseField::from_u32_unchecked((i * N_LANES) as u32)) + inc; - trace[1].data[i] = *id; + inc + PackedM31::broadcast(M31::from_u32_unchecked((i * N_LANES) as u32)); } - // TODO(Ohad): avoid copy - trace[2] = BaseColumn::from_iter( - self.multiplicities - .clone() - .into_iter() - .map(BaseField::from_u32_unchecked), - ); + // TODO(Ohad): Replace with seq. + for (i, (id, multiplicity)) in zip(id_it, multiplicities_it).enumerate() { + let chunk_idx = i / packed_size; + let i = i % packed_size; + trace[1 + chunk_idx * N_ID_AND_MULT_COLUMNS_PER_CHUNK].data[i] = id; + trace[2 + chunk_idx * N_ID_AND_MULT_COLUMNS_PER_CHUNK].data[i] = multiplicity; + } // Lookup data. let addresses = trace[0].data.clone(); - let ids = trace[1].data.clone(); - let multiplicities = trace[2].data.clone(); + let ids: [_; SPLIT_SIZE] = + std::array::from_fn(|i| trace[1 + i * N_ID_AND_MULT_COLUMNS_PER_CHUNK].data.clone()); + let multiplicities: [_; SPLIT_SIZE] = + std::array::from_fn(|i| trace[2 + i * N_ID_AND_MULT_COLUMNS_PER_CHUNK].data.clone()); // Commit on trace. - let log_address_bound = size.checked_ilog2().unwrap(); - let domain = CanonicCoset::new(log_address_bound).circle_domain(); + let log_size = size.checked_ilog2().unwrap(); + let domain = CanonicCoset::new(log_size).circle_domain(); let trace = trace .into_iter() .map(|eval| CircleEvaluation::::new(domain, eval)) @@ -120,9 +124,7 @@ impl ClaimGenerator { tree_builder.extend_evals(trace); ( - Claim { - log_size: log_address_bound, - }, + Claim { log_size }, InteractionClaimGenerator { addresses, ids, @@ -134,34 +136,30 @@ impl ClaimGenerator { pub struct InteractionClaimGenerator { pub addresses: Vec, - pub ids: Vec, - pub multiplicities: Vec, + pub ids: [Vec; SPLIT_SIZE], + pub multiplicities: [Vec; SPLIT_SIZE], } impl InteractionClaimGenerator { - pub fn with_capacity(capacity: usize) -> Self { - Self { - addresses: Vec::with_capacity(capacity), - ids: Vec::with_capacity(capacity), - multiplicities: Vec::with_capacity(capacity), - } - } - pub fn write_interaction_trace( self, tree_builder: &mut TreeBuilder<'_, '_, SimdBackend, Blake2sMerkleChannel>, lookup_elements: &relations::MemoryAddressToId, ) -> InteractionClaim { - let log_size = self.addresses.len().ilog2() + LOG_N_LANES; + let packed_size = self.addresses.len(); + let log_size = packed_size.ilog2() + LOG_N_LANES; let mut logup_gen = LogupTraceGenerator::new(log_size); - let mut col_gen = logup_gen.new_col(); - for vec_row in 0..1 << (log_size - LOG_N_LANES) { - let addr = self.addresses[vec_row]; - let id = self.ids[vec_row]; - let denom: PackedSecureField = lookup_elements.combine(&[addr, id]); - col_gen.write_frac(vec_row, (-self.multiplicities[vec_row]).into(), denom); + for (i, (ids, multiplicities)) in izip!(&self.ids, &self.multiplicities).enumerate() { + let mut col_gen = logup_gen.new_col(); + for (vec_row, (&addr, &id, &mult)) in + izip!(&self.addresses, ids, multiplicities).enumerate() + { + let addr = addr + PackedM31::broadcast(M31((i * (1 << log_size)) as u32)); + let denom = lookup_elements.combine(&[addr, id]); + col_gen.write_frac(vec_row, (-mult).into(), denom); + } + col_gen.finalize_col(); } - col_gen.finalize_col(); let (trace, claimed_sum) = logup_gen.finalize_last(); tree_builder.extend_evals(trace); diff --git a/stwo_cairo_prover/out.txt b/stwo_cairo_prover/out.txt new file mode 100644 index 00000000..b3d7545e --- /dev/null +++ b/stwo_cairo_prover/out.txt @@ -0,0 +1,240 @@ + +running 0 tests + +successes: + +successes: + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s + + +running 0 tests + +successes: + +successes: + +test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 4 filtered out; finished in 0.00s + + +running 1 test +test cairo_air::tests::test_basic_cairo_air has been running for over 60 seconds +test cairo_air::tests::test_basic_cairo_air ... FAILED + +successes: + +successes: + +failures: + +---- cairo_air::tests::test_basic_cairo_air stdout ---- +ids: [0, 0, 1073741824, 1, 2, 3, 4, 0, 5, 6, 7, 8, 9, 10, 7, 8, 11, 12, 1073741825, 6, 1073741826, 13, 14, 15, 1073741824, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 5, 14, 9, 16, 17, 18, 19, 20, 18, 21, 22, 18, 11, 23, 18, 24, 25, 18, 7, 26, 18, 27, 28, 18, 29, 30, 18, 2, 31, 18, 15, 32, 18, 0] +multiplicities: [1, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 11, 2, 2, 2, 11, 2, 11, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 2, 2, 3, 2, 2, 3, 2, 2, 3, 2, 2, 3, 2, 2, 3, 2, 2, 3, 2, 2, 3, 2, 2, 3, 2, 2, 3, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] +Relations summary: MemoryAddressToId: + [96, 18] -> 2 + +addr: PackedM31([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]) +id: PackedM31([0, 0, 1073741824, 1, 2, 3, 4, 0, 5, 6, 7, 8, 9, 10, 7, 8]) +mult: PackedM31([1, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 2, 11, 2]) +addr: PackedM31([16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31]) +id: PackedM31([11, 12, 1073741825, 6, 1073741826, 13, 14, 15, 1073741824, 0, 0, 0, 0, 0, 0, 0]) +mult: PackedM31([2, 2, 11, 2, 11, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]) +addr: PackedM31([32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47]) +id: PackedM31([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +mult: PackedM31([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +addr: PackedM31([48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]) +id: PackedM31([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]) +mult: PackedM31([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) +addr: PackedM31([64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79]) +id: PackedM31([5, 14, 9, 16, 17, 18, 19, 20, 18, 21, 22, 18, 11, 23, 18, 24]) +mult: PackedM31([3, 2, 2, 3, 2, 2, 3, 2, 2, 3, 2, 2, 3, 2, 2, 3]) + +addr: PackedM31([96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111]) +id: PackedM31([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +mult: PackedM31([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +addr: PackedM31([112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127]) +id: PackedM31([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +mult: PackedM31([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) +CairoComponents +Opcodes: add_f_f: + +add_f_t: + +add_t_f: + +add_t_t: + + n_rows 2^4 + n_constraints 20 + constraint_log_degree_bound 5 + total felts: 2^4 * 63 + Interaction 0: n_cols 1 + Interaction 1: n_cols 26 + Interaction 2: n_cols 36 +add_ap_f_f: + +add_ap_f_t: + +add_ap_t_f: + + n_rows 2^4 + n_constraints 9 + constraint_log_degree_bound 5 + total felts: 2^4 * 30 + Interaction 0: n_cols 1 + Interaction 1: n_cols 9 + Interaction 2: n_cols 20 +assert_eq_f_f: + +assert_eq_f_t: + + n_rows 2^4 + n_constraints 6 + constraint_log_degree_bound 5 + total felts: 2^4 * 28 + Interaction 0: n_cols 1 + Interaction 1: n_cols 7 + Interaction 2: n_cols 20 +assert_eq_t_f: + +call_f_f: + +call_f_t: + +call_t_f: + + n_rows 2^4 + n_constraints 15 + constraint_log_degree_bound 5 + total felts: 2^4 * 54 + Interaction 0: n_cols 1 + Interaction 1: n_cols 17 + Interaction 2: n_cols 36 +generic: + +jnz_f_f: + +jnz_f_t: + + n_rows 2^4 + n_constraints 7 + constraint_log_degree_bound 5 + total felts: 2^4 * 55 + Interaction 0: n_cols 1 + Interaction 1: n_cols 34 + Interaction 2: n_cols 20 +jnz_t_f: + +jnz_t_t: + + n_rows 2^4 + n_constraints 13 + constraint_log_degree_bound 5 + total felts: 2^4 * 71 + Interaction 0: n_cols 1 + Interaction 1: n_cols 42 + Interaction 2: n_cols 28 +jump_f_f_f: + +jump_f_f_t: + +jump_t_f_f: + +jump_t_t_f: + + n_rows 2^4 + n_constraints 9 + constraint_log_degree_bound 5 + total felts: 2^4 * 31 + Interaction 0: n_cols 1 + Interaction 1: n_cols 10 + Interaction 2: n_cols 20 +mul_f_f: + +mul_f_t: + +ret: + + n_rows 2^4 + n_constraints 8 + constraint_log_degree_bound 5 + total felts: 2^4 * 40 + Interaction 0: n_cols 1 + Interaction 1: n_cols 11 + Interaction 2: n_cols 28 + +VerifyInstruction: + n_rows 2^4 + n_constraints 23 + constraint_log_degree_bound 5 + total felts: 2^4 * 50 + Interaction 0: n_cols 1 + Interaction 1: n_cols 29 + Interaction 2: n_cols 20 +MemoryAddressToId: + n_rows 2^5 + n_constraints 4 + constraint_log_degree_bound 6 + total felts: 2^5 * 26 + Interaction 0: n_cols 1 + Interaction 1: n_cols 9 + Interaction 2: n_cols 16 +MemoryIdToValue: + n_rows 2^4 + n_constraints 15 + constraint_log_degree_bound 5 + total felts: 2^4 * 91 + Interaction 0: n_cols 1 + Interaction 1: n_cols 30 + Interaction 2: n_cols 60 +SmallMemoryIdToValue: + n_rows 2^6 + n_constraints 5 + constraint_log_degree_bound 7 + total felts: 2^6 * 31 + Interaction 0: n_cols 1 + Interaction 1: n_cols 10 + Interaction 2: n_cols 20 +RangeCheck19: + n_rows 2^19 + n_constraints 1 + constraint_log_degree_bound 20 + total felts: 2^19 * 7 + Interaction 0: n_cols 1 + Interaction 1: n_cols 2 + Interaction 2: n_cols 4 +RangeCheck9_9: + n_rows 2^18 + n_constraints 1 + constraint_log_degree_bound 19 + total felts: 2^18 * 8 + Interaction 0: n_cols 1 + Interaction 1: n_cols 3 + Interaction 2: n_cols 4 +RangeCheck7_2_5: + n_rows 2^14 + n_constraints 1 + constraint_log_degree_bound 15 + total felts: 2^14 * 9 + Interaction 0: n_cols 1 + Interaction 1: n_cols 4 + Interaction 2: n_cols 4 +RangeCheck4_3: + n_rows 2^7 + n_constraints 1 + constraint_log_degree_bound 8 + total felts: 2^7 * 8 + Interaction 0: n_cols 1 + Interaction 1: n_cols 3 + Interaction 2: n_cols 4 + +thread 'cairo_air::tests::test_basic_cairo_air' panicked at crates/prover/src/cairo_air/mod.rs:191:35: +called `Result::unwrap()` on an `Err` value: InvalidLogupSum +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + +failures: + cairo_air::tests::test_basic_cairo_air + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 22 filtered out; finished in 64.11s +