Skip to content
17 changes: 15 additions & 2 deletions crypto/stark/src/instruments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,19 @@ pub struct Round1SubOps {
pub aux_merkle: Duration,
}

/// Per-table accounting captured during rounds 2-4. Includes shape (rows ×
/// main/aux cols) so callers can compute total main-trace cells (the
/// `sum(rows × main_cols)` headline metric used to compare against ZisK / SP1).
#[derive(Clone, Debug, Default)]
pub struct TableTiming {
pub name: String,
pub rows: usize,
pub main_cols: usize,
pub aux_cols: usize,
pub duration: Duration,
pub sub_ops: TableSubOps,
}

/// Timing data collected inside `multi_prove`.
pub struct MultiProveTiming {
pub prepass: Duration,
Expand All @@ -69,8 +82,8 @@ pub struct MultiProveTiming {
pub rounds_2_4: Duration,
/// Sub-op breakdown for Round 1 (main + aux LDE vs Merkle).
pub round1_sub: Round1SubOps,
/// (name, rows, duration, sub_ops) per table for rounds 2-4.
pub table_timings: Vec<(String, usize, Duration, TableSubOps)>,
/// Shape + timing for every AIR proven this run.
pub table_timings: Vec<TableTiming>,
pub heap_snapshots: Vec<HeapSnapshot>,
}

Expand Down
19 changes: 8 additions & 11 deletions crypto/stark/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1855,12 +1855,7 @@ pub trait IsStarkProver<
#[cfg(feature = "instruments")]
let phase_start = Instant::now();
#[cfg(feature = "instruments")]
let mut table_timings: Vec<(
String,
usize,
std::time::Duration,
crate::instruments::TableSubOps,
)> = Vec::with_capacity(num_airs);
let mut table_timings: Vec<crate::instruments::TableTiming> = Vec::with_capacity(num_airs);

let mut proofs = Vec::with_capacity(num_airs);
let mut lde_drain = cached_ldes.into_iter();
Expand Down Expand Up @@ -1919,12 +1914,14 @@ pub trait IsStarkProver<
#[cfg(feature = "instruments")]
let table_timing = {
let sub_ops = crate::instruments::take_round_sub_ops().unwrap_or_default();
(
air.name().to_string(),
trace.num_rows(),
table_start.elapsed(),
crate::instruments::TableTiming {
name: air.name().to_string(),
rows: trace.num_rows(),
main_cols: trace.num_main_columns,
aux_cols: trace.num_aux_columns,
duration: table_start.elapsed(),
sub_ops,
)
}
};

#[cfg(feature = "instruments")]
Expand Down
102 changes: 90 additions & 12 deletions prover/src/instruments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ fn fmt_rows(rows: usize) -> String {
}
}

fn fmt_cells(cells: u128) -> String {
if cells >= 1_000_000_000 {
format!("{:.2}G", cells as f64 / 1_000_000_000.0)
} else if cells >= 1_000_000 {
format!("{:.1}M", cells as f64 / 1_000_000.0)
} else if cells >= 1_000 {
format!("{}K", (cells + 500) / 1_000)
} else {
format!("{cells}")
}
}

fn pct(dur: Duration, total: Duration) -> f64 {
if total > Duration::ZERO {
dur.as_secs_f64() / total.as_secs_f64() * 100.0
Expand Down Expand Up @@ -94,25 +106,25 @@ pub fn print_report(

// Merge split tables: MEMW[0..4] → MEMW x5
let mut merged: BTreeMap<String, MergedTable> = BTreeMap::new();
for (name, rows, dur, sub_ops) in &mp.table_timings {
let base = base_name(name).to_string();
for tt in &mp.table_timings {
let base = base_name(&tt.name).to_string();
let entry = merged.entry(base).or_insert(MergedTable {
total_dur: Duration::ZERO,
total_rows: 0,
count: 0,
sub_ops: stark::instruments::TableSubOps::default(),
});
entry.total_dur += *dur;
entry.total_rows += rows;
entry.total_dur += tt.duration;
entry.total_rows += tt.rows;
entry.count += 1;
entry.sub_ops.constraints += sub_ops.constraints;
entry.sub_ops.comp_decompose += sub_ops.comp_decompose;
entry.sub_ops.comp_commit += sub_ops.comp_commit;
entry.sub_ops.ood += sub_ops.ood;
entry.sub_ops.deep_comp += sub_ops.deep_comp;
entry.sub_ops.deep_extend += sub_ops.deep_extend;
entry.sub_ops.fri_commit += sub_ops.fri_commit;
entry.sub_ops.queries += sub_ops.queries;
entry.sub_ops.constraints += tt.sub_ops.constraints;
entry.sub_ops.comp_decompose += tt.sub_ops.comp_decompose;
entry.sub_ops.comp_commit += tt.sub_ops.comp_commit;
entry.sub_ops.ood += tt.sub_ops.ood;
entry.sub_ops.deep_comp += tt.sub_ops.deep_comp;
entry.sub_ops.deep_extend += tt.sub_ops.deep_extend;
entry.sub_ops.fri_commit += tt.sub_ops.fri_commit;
entry.sub_ops.queries += tt.sub_ops.queries;
}

let mut sorted: Vec<_> = merged.into_iter().collect();
Expand Down Expand Up @@ -212,6 +224,72 @@ pub fn print_report(
total_merkle.as_secs_f64(),
pct(total_merkle, total)
);

// Per-AIR main-trace cell breakdown. The headline metric used to
// compare against ZisK / SP1 is the sum of `rows × main_cols` across
// all AIRs. Aux cells are printed separately because they reflect
// LogUp/permutation overhead and don't contribute to the ZisK-style
// "main-trace cells" baseline.
let mut by_table: BTreeMap<String, (usize, usize, u128, u128, usize)> = BTreeMap::new();
for tt in &mp.table_timings {
let base = base_name(&tt.name).to_string();
let entry = by_table.entry(base).or_insert((0, tt.main_cols, 0, 0, 0));
entry.0 += tt.rows;
entry.2 += (tt.rows as u128) * (tt.main_cols as u128);
entry.3 += (tt.rows as u128) * (tt.aux_cols as u128);
entry.4 += 1;
// tt.aux_cols may differ across instances of a split table; track the
// last value seen (split tables share aux_col count by construction).
entry.1 = tt.main_cols;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Low — comment says "aux_col count" but updates main_cols

entry.1 is the main_cols slot. The comment is a copy-paste error: it says aux_col count but both the field updated and the value assigned are main_cols.

Suggested change
entry.1 = tt.main_cols;
// main_cols is identical for every chunk of a split table.
entry.1 = tt.main_cols;

}
let mut cell_rows: Vec<(String, usize, usize, u128, u128, usize)> = by_table
.into_iter()
.map(|(name, (rows, main_cols, main_cells, aux_cells, count))| {
(name, rows, main_cols, main_cells, aux_cells, count)
})
.collect();
cell_rows.sort_by(|a, b| b.3.cmp(&a.3));

let mut total_main_cells: u128 = 0;
let mut total_aux_cells: u128 = 0;
let mut total_table_rows: usize = 0;
for r in &cell_rows {
total_main_cells += r.3;
total_aux_cells += r.4;
total_table_rows += r.1;
}

eprintln!();
eprintln!("=== TRACE CELLS (rows × main_cols) ===");
eprintln!(
" {:<22} {:>6} {:>5} {:>10} {:>10}",
"Table", "rows", "cols", "main", "aux",
);
eprintln!(" {}", "─".repeat(58));
for (name, rows, main_cols, main_cells, aux_cells, count) in &cell_rows {
let display_name = if *count > 1 {
format!("{name} x{count}")
} else {
name.clone()
};
eprintln!(
" {:<22} {:>6} {:>5} {:>10} {:>10}",
display_name,
fmt_rows(*rows),
main_cols,
fmt_cells(*main_cells),
fmt_cells(*aux_cells),
);
}
eprintln!(" {}", "─".repeat(58));
eprintln!(
" {:<22} {:>6} {:>5} {:>10} {:>10}",
"TOTAL",
fmt_rows(total_table_rows),
"",
fmt_cells(total_main_cells),
fmt_cells(total_aux_cells),
);
}

eprintln!(" {}", "─".repeat(58));
Expand Down
36 changes: 29 additions & 7 deletions prover/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,18 @@ use stark::verifier::{IsStarkVerifier, Verifier};

pub use crate::tables::MaxRowsConfig;
use crate::tables::bitwise;
use crate::tables::byte_ops;
use crate::tables::decode;
use crate::tables::page;
use crate::tables::register;
use crate::tables::trace_builder::Traces;
use crate::tables::types::BusId;
use crate::test_utils::{
E, F, VmAir, create_bitwise_air, create_branch_air, create_commit_air, create_cpu_air,
create_decode_air, create_dvrm_air, create_halt_air, create_load_air, create_lt_air,
create_memw_air, create_memw_aligned_air, create_memw_register_air, create_mul_air,
create_page_air, create_register_air, create_shift_air,
E, F, VmAir, create_bitwise_air, create_branch_air, create_byte_ops_air, create_commit_air,
create_cpu_air, create_cpu_bitwise_air, create_decode_air, create_dvrm_air, create_halt_air,
create_load_air, create_lt_air, create_memw_air, create_memw_aligned_air,
create_memw_register_air, create_mul_air, create_page_air, create_register_air,
create_shift_air,
};

use stark::proof::options::{GoldilocksCubicProofOptions, ProofOptions};
Expand Down Expand Up @@ -188,7 +190,9 @@ type AirTracePair<'a> = (
/// All VM AIR instances, grouped by table.
pub(crate) struct VmAirs {
pub cpus: Vec<VmAir>,
pub cpu_bitwise: VmAir,
pub bitwise: VmAir,
pub byte_ops: VmAir,
pub lts: Vec<VmAir>,
pub shifts: Vec<VmAir>,
pub memws: Vec<VmAir>,
Expand All @@ -210,6 +214,8 @@ impl VmAirs {
pub fn air_trace_pairs<'a>(&'a self, traces: &'a mut Traces) -> Vec<AirTracePair<'a>> {
let mut pairs: Vec<AirTracePair<'a>> = vec![
(&self.bitwise, &mut traces.bitwise, &()),
(&self.byte_ops, &mut traces.byte_ops, &()),
(&self.cpu_bitwise, &mut traces.cpu_bitwise, &()),
(&self.decode, &mut traces.decode, &()),
(&self.halt, &mut traces.halt, &()),
(&self.commit, &mut traces.commit, &()),
Expand Down Expand Up @@ -265,6 +271,8 @@ impl VmAirs {
pub fn air_refs(&self) -> Vec<&dyn AIR<Field = F, FieldExtension = E, PublicInputs = ()>> {
let mut refs: Vec<&dyn AIR<Field = F, FieldExtension = E, PublicInputs = ()>> = vec![
&self.bitwise,
&self.byte_ops,
&self.cpu_bitwise,
&self.decode,
&self.halt,
&self.commit,
Expand Down Expand Up @@ -324,6 +332,7 @@ impl VmAirs {
let cpus: Vec<_> = (0..table_counts.cpu)
.map(|i| create_cpu_air(proof_options).with_name(&format!("CPU[{}]", i)))
.collect();
let cpu_bitwise = create_cpu_bitwise_air(proof_options);
let bitwise = if minimal_bitwise {
create_bitwise_air(proof_options)
} else {
Expand All @@ -332,6 +341,14 @@ impl VmAirs {
bitwise::NUM_PRECOMPUTED_COLS,
)
};
let byte_ops = if minimal_bitwise {
create_byte_ops_air(proof_options)
} else {
create_byte_ops_air(proof_options).with_preprocessed(
byte_ops::preprocessed_commitment(proof_options),
byte_ops::NUM_PRECOMPUTED_COLS,
)
};
let lts: Vec<_> = (0..table_counts.lt)
.map(|i| create_lt_air(proof_options).with_name(&format!("LT[{}]", i)))
.collect();
Expand Down Expand Up @@ -394,7 +411,9 @@ impl VmAirs {

Self {
cpus,
cpu_bitwise,
bitwise,
byte_ops,
lts,
shifts,
memws,
Expand Down Expand Up @@ -690,11 +709,14 @@ pub fn verify_with_options(
);

// Cross-check: table_counts must match the number of sub-proofs.
// Fixed tables (bitwise, decode, halt, commit, register) = 5, plus page tables.
let expected_proof_count = vm_proof.table_counts.total() + 5 + page_configs.len();
// Fixed tables = 7: bitwise, byte_ops, cpu_bitwise, decode, halt, commit, register.
// Plus one page table per page_config.
const NUM_FIXED_TABLES: usize = 7;
let expected_proof_count =
vm_proof.table_counts.total() + NUM_FIXED_TABLES + page_configs.len();
if expected_proof_count != vm_proof.proof.proofs.len() {
return Err(Error::InvalidTableCounts(format!(
"table_counts total ({}) + 5 fixed + {} pages = {}, but proof contains {} sub-proofs",
"table_counts total ({}) + {NUM_FIXED_TABLES} fixed + {} pages = {}, but proof contains {} sub-proofs",
vm_proof.table_counts.total(),
page_configs.len(),
expected_proof_count,
Expand Down
Loading
Loading