Skip to main content

openvm_stark_backend/prover/
metrics.rs

1use std::fmt::Display;
2
3use itertools::zip_eq;
4use serde::{Deserialize, Serialize};
5use tracing::{debug, info};
6
7use crate::{
8    keygen::types::TraceWidth,
9    proof::TraceVData,
10    prover::{DeviceMultiStarkProvingKey, ProverBackend},
11    StarkProtocolConfig,
12};
13
14#[derive(Clone, Debug, Serialize, Deserialize)]
15pub struct TraceMetrics {
16    pub per_air: Vec<SingleTraceMetrics>,
17    /// Total base field cells from all traces, excludes preprocessed.
18    pub total_cells: usize,
19    /// For each trace height constraint, the (weighted sum, threshold)
20    pub trace_height_inequalities: Vec<(usize, usize)>,
21}
22
23#[derive(Clone, Debug, Serialize, Deserialize)]
24pub struct SingleTraceMetrics {
25    pub air_name: String,
26    pub air_id: usize,
27    pub height: usize,
28    pub width: TraceWidth,
29    pub cells: TraceCells,
30    // TODO[jpw]: update this calculation accordingly
31    /// Omitting preprocessed trace, the total base field cells from main and after challenge
32    /// traces.
33    pub total_cells: usize,
34}
35
36/// Trace cells, counted in terms of number of **base field** elements.
37#[derive(Clone, Debug, Serialize, Deserialize)]
38pub struct TraceCells {
39    pub preprocessed: Option<usize>,
40    pub cached_mains: Vec<usize>,
41    pub common_main: usize,
42}
43
44impl Display for TraceMetrics {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        for (i, (weighted_sum, threshold)) in self.trace_height_inequalities.iter().enumerate() {
47            writeln!(
48                f,
49                "trace_height_constraint_{i} | weighted_sum = {:<10} | threshold = {:<10}",
50                format_number_with_underscores(*weighted_sum),
51                format_number_with_underscores(*threshold)
52            )?;
53        }
54        for trace_metrics in &self.per_air {
55            writeln!(f, "{}", trace_metrics)?;
56        }
57        Ok(())
58    }
59}
60
61impl Display for SingleTraceMetrics {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        write!(
64            f,
65            "{:<20} | Rows = {:<10} | Cells = {:<11} | Prep Cols = {:<5} | Main Cols = {:<5}",
66            self.air_name,
67            format_number_with_underscores(self.height),
68            format_number_with_underscores(self.total_cells),
69            self.width.preprocessed.unwrap_or(0),
70            format!("{:?}", self.width.main_widths()),
71        )?;
72        Ok(())
73    }
74}
75
76/// heights are the trace heights for each air
77pub fn trace_metrics<SC: StarkProtocolConfig, PB: ProverBackend>(
78    mpk: &DeviceMultiStarkProvingKey<PB>,
79    trace_vdata: &[Option<TraceVData<SC>>],
80) -> TraceMetrics {
81    let heights = trace_vdata
82        .iter()
83        .map(|vdata| vdata.as_ref().map(|v| 1 << v.log_height).unwrap_or(0))
84        .collect::<Vec<_>>();
85    let trace_height_inequalities = mpk
86        .trace_height_constraints
87        .iter()
88        .map(|trace_height_constraint| {
89            let weighted_sum = heights
90                .iter()
91                .enumerate()
92                .map(|(air_idx, h)| (trace_height_constraint.coefficients[air_idx] as usize) * h)
93                .sum::<usize>();
94            (weighted_sum, trace_height_constraint.threshold as usize)
95        })
96        .collect::<Vec<_>>();
97    let per_air: Vec<_> = zip_eq(&mpk.per_air, heights)
98        .enumerate()
99        .filter(|(_, (_, height))| *height > 0)
100        .map(|(air_idx, (pk, height))| {
101            let air_name = &pk.air_name;
102            let width = pk.vk.params.width.clone();
103            let cells = TraceCells {
104                preprocessed: width.preprocessed.map(|w| w * height),
105                cached_mains: width.cached_mains.iter().map(|w| w * height).collect(),
106                common_main: width.common_main * height,
107            };
108            let total_cells = cells
109                .cached_mains
110                .iter()
111                .chain([&cells.common_main])
112                .sum::<usize>();
113            SingleTraceMetrics {
114                air_name: air_name.to_string(),
115                air_id: air_idx,
116                height,
117                width,
118                cells,
119                total_cells,
120            }
121        })
122        .collect();
123    let total_cells = per_air.iter().map(|m| m.total_cells).sum();
124    let metrics = TraceMetrics {
125        per_air,
126        total_cells,
127        trace_height_inequalities,
128    };
129    info!(
130        "total_trace_cells = {} (excluding preprocessed)",
131        format_number_with_underscores(metrics.total_cells)
132    );
133    info!(
134        "preprocessed_trace_cells = {}",
135        format_number_with_underscores(
136            metrics
137                .per_air
138                .iter()
139                .map(|m| m.cells.preprocessed.unwrap_or(0))
140                .sum::<usize>()
141        )
142    );
143    info!(
144        "main_trace_cells = {}",
145        format_number_with_underscores(
146            metrics
147                .per_air
148                .iter()
149                .map(|m| m.cells.cached_mains.iter().sum::<usize>() + m.cells.common_main)
150                .sum::<usize>()
151        )
152    );
153    debug!("{}", metrics);
154    metrics
155}
156
157pub fn format_number_with_underscores(n: usize) -> String {
158    let num_str = n.to_string();
159    let mut result = String::new();
160
161    // Start adding characters from the end of num_str
162    for (i, c) in num_str.chars().rev().enumerate() {
163        if i > 0 && i % 3 == 0 {
164            result.push('_');
165        }
166        result.push(c);
167    }
168
169    // Reverse the result to get the correct order
170    result.chars().rev().collect()
171}
172
173#[cfg(feature = "metrics")]
174mod emit {
175    use metrics::counter;
176
177    use super::{SingleTraceMetrics, TraceMetrics};
178
179    impl TraceMetrics {
180        pub fn emit(&self) {
181            for (i, (weighted_sum, threshold)) in self.trace_height_inequalities.iter().enumerate()
182            {
183                let labels = [("trace_height_constraint", i.to_string())];
184                counter!("weighted_sum", &labels).absolute(*weighted_sum as u64);
185                counter!("threshold", &labels).absolute(*threshold as u64);
186            }
187            for trace_metrics in &self.per_air {
188                trace_metrics.emit();
189            }
190            counter!("total_cells").absolute(self.total_cells as u64);
191        }
192    }
193
194    impl SingleTraceMetrics {
195        pub fn emit(&self) {
196            let labels = [
197                ("air_name", self.air_name.clone()),
198                ("air_id", self.air_id.to_string()),
199            ];
200            counter!("rows", &labels).absolute(self.height as u64);
201            counter!("cells", &labels).absolute(self.total_cells as u64);
202            counter!("prep_cols", &labels).absolute(self.width.preprocessed.unwrap_or(0) as u64);
203            counter!("main_cols", &labels).absolute(
204                (self.width.cached_mains.iter().sum::<usize>() + self.width.common_main) as u64,
205            );
206        }
207    }
208}