use std::{cmp::max, mem::size_of};
use crate::{StarkProtocolConfig, SystemParams};
pub const INTERACTION_MEMORY_OVERHEAD: usize = 2 << 20;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ProvingMemoryCounts {
pub main_cells_with_rot: usize,
pub main_cells_without_rot: usize,
pub interaction_cells: usize,
}
impl ProvingMemoryCounts {
pub const fn new(
main_cells_with_rot: usize,
main_cells_without_rot: usize,
interaction_cells: usize,
) -> Self {
Self {
main_cells_with_rot,
main_cells_without_rot,
interaction_cells,
}
}
#[inline]
pub const fn main_cells(&self) -> usize {
self.main_cells_with_rot + self.main_cells_without_rot
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ProvingMemoryEstimate {
pub total: usize,
pub main: usize,
pub rs_code_matrix: usize,
pub main_secondary: usize,
pub interaction: usize,
pub secondary_peak: usize,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ProvingMemoryConfig {
pub base_field_size: usize,
pub extension_degree: usize,
pub log_blowup: usize,
pub l_skip: usize,
pub max_constraint_degree: usize,
pub cache_rs_code_matrix: bool,
}
impl ProvingMemoryConfig {
pub fn from_protocol_config<SC: StarkProtocolConfig>(
config: &SC,
cache_rs_code_matrix: bool,
) -> Self {
Self::from_params::<SC::F>(config.params(), SC::D_EF, cache_rs_code_matrix)
}
fn from_params<F>(
params: &SystemParams,
extension_degree: usize,
cache_rs_code_matrix: bool,
) -> Self {
Self {
base_field_size: size_of::<F>(),
extension_degree,
log_blowup: params.log_blowup,
l_skip: params.l_skip,
max_constraint_degree: params.max_constraint_degree,
cache_rs_code_matrix,
}
}
#[inline]
pub fn main_memory_bytes(&self, main_cells: usize) -> usize {
main_cells * self.base_field_size
}
#[inline]
pub fn rs_code_matrix_memory_bytes(&self, main_cells: usize) -> usize {
main_cells * (1usize << self.log_blowup) * self.base_field_size
}
#[inline]
pub fn main_cell_secondary_weight(&self) -> f64 {
default_main_cell_secondary_weight(
self.extension_degree,
self.l_skip,
self.max_constraint_degree,
)
}
#[inline]
pub fn main_secondary_memory_bytes_for_rot(&self, main_cells: usize, need_rot: bool) -> usize {
let main_cell_secondary_weight = self.main_cell_secondary_weight();
let weight = if need_rot {
2.0 * main_cell_secondary_weight
} else {
main_cell_secondary_weight
};
ceil_weighted_bytes(main_cells, self.base_field_size, weight)
}
#[inline]
pub fn main_secondary_memory_bytes(&self, counts: ProvingMemoryCounts) -> usize {
self.main_secondary_memory_bytes_for_rot(counts.main_cells_with_rot, true)
+ self.main_secondary_memory_bytes_for_rot(counts.main_cells_without_rot, false)
}
#[inline]
pub fn interaction_memory_bytes_without_overhead(&self, interaction_cells: usize) -> usize {
ceil_weighted_bytes(
interaction_cells,
self.base_field_size,
default_interaction_cell_weight(self.extension_degree),
)
}
#[inline]
pub fn interaction_memory_bytes(&self, interaction_cells: usize) -> usize {
self.interaction_memory_bytes_without_overhead(interaction_cells)
+ INTERACTION_MEMORY_OVERHEAD
}
#[inline]
pub fn estimate(&self, counts: ProvingMemoryCounts) -> ProvingMemoryEstimate {
let main_cells = counts.main_cells();
let main = self.main_memory_bytes(main_cells);
let rs_code_matrix = self.rs_code_matrix_memory_bytes(main_cells);
let main_secondary = self.main_secondary_memory_bytes(counts);
let interaction = self.interaction_memory_bytes(counts.interaction_cells);
let secondary_peak = if self.cache_rs_code_matrix {
rs_code_matrix + max(main_secondary, interaction)
} else {
max(rs_code_matrix, max(main_secondary, interaction))
};
ProvingMemoryEstimate {
total: main + secondary_peak,
main,
rs_code_matrix,
main_secondary,
interaction,
secondary_peak,
}
}
}
fn default_main_cell_secondary_weight(
extension_degree: usize,
l_skip: usize,
max_constraint_degree: usize,
) -> f64 {
(1.0 + max_constraint_degree as f64 / 2.0) * extension_degree as f64 / (1usize << l_skip) as f64
}
fn default_interaction_cell_weight(extension_degree: usize) -> f64 {
(2 * extension_degree) as f64 * (1.0 + 2.0 * (1.0 / 16.0 + 1.0 / 256.0))
}
fn ceil_weighted_bytes(cell_count: usize, base_field_size: usize, weight: f64) -> usize {
((cell_count * base_field_size) as f64 * weight).ceil() as usize
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::default_test_params_small;
fn test_memory_config() -> ProvingMemoryConfig {
let params = default_test_params_small();
ProvingMemoryConfig::from_params::<u32>(¶ms, 4, true)
}
#[test]
fn dropped_rs_code_matrix_is_phase_disjoint() {
let params = default_test_params_small();
let config = ProvingMemoryConfig::from_params::<u32>(¶ms, 4, false);
let counts = ProvingMemoryCounts::new(10, 20, 5);
let estimate = config.estimate(counts);
assert_eq!(estimate.main, 30 * 4);
assert_eq!(estimate.rs_code_matrix, 30 * 2 * 4);
assert_eq!(estimate.total, estimate.main + estimate.secondary_peak);
assert_eq!(
estimate.secondary_peak,
max(
estimate.rs_code_matrix,
max(estimate.main_secondary, estimate.interaction)
)
);
}
#[test]
fn cached_rs_code_matrix_is_additive() {
let config = test_memory_config();
let counts = ProvingMemoryCounts::new(10, 20, 5);
let estimate = config.estimate(counts);
assert_eq!(
estimate.secondary_peak,
estimate.rs_code_matrix + max(estimate.main_secondary, estimate.interaction)
);
}
}