Skip to content

Commit 357226e

Browse files
committed
test(lfm): the base-layer A/B — the real block's epoch 0, per-table vs batched MMCS
The census harness's exact epoch-0 construction (same executor slice, same traces, same L2G bookend, same statement-seeded transcript), with only the prove call differing: Prover::multi_prove vs multi_prove_batched, one arm per process, residency moved by the one shared lever. Neither arm verifies, stated in the header: the per-table construction is production-accepted by the wrap census's own gate on every run, and the batched arm cannot verify until a pinned preprocessed round exists for the VM AIR set — the VM-side analogue of the M-8 round-coverage gap, on which the batched verifier fails closed by design. This instrument is what makes blake3-full-mmcs a branch whose BASE layer runs the batched commitment scheme, measurably.
1 parent 74d9442 commit 357226e

1 file changed

Lines changed: 192 additions & 0 deletions

File tree

‎prover/src/lfm/epoch_tests.rs‎

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,198 @@ impl EpochInputs {
697697
/// is the fibonacci fixture unless a measurement run overrode it — so two runs
698698
/// at different options stay comparable, and assembly ledger entry 10 still
699699
/// holds: the trace-length profile travels with every number.
700+
/// ★ THE BASE-LAYER A/B — the real block's epoch 0 proved per-table vs
701+
/// BATCHED-MMCS, one arm per process.
702+
///
703+
/// `AB_MODE` selects the arm (`per_table` | `batched`); `LAMBDA_VM_RESIDENCY`
704+
/// moves BOTH arms through the same lever, so a residency difference between
705+
/// them cannot be an artifact of two code paths reading two knobs. Peak anon
706+
/// is a process-lifetime high-water mark, measured by the harness around the
707+
/// process — two arms sharing a process would each report the larger of the
708+
/// two and the comparison would be vacuous.
709+
///
710+
/// The epoch construction is byte-for-byte the census harness's
711+
/// ([`real_epoch_from`]): same executor slice, same traces, same L2G bookend,
712+
/// same statement-seeded transcript. Only the prove call differs.
713+
///
714+
/// ⚠ NEITHER arm verifies here, deliberately. The per-table construction is
715+
/// production-accepted every time `the_real_block_epoch_wraps` runs (its own
716+
/// gate), so it needs no second acceptance; the batched arm CANNOT verify —
717+
/// no pinned preprocessed round exists for the VM AIR set (the per-table
718+
/// verifier pins each table's root individually; the batched verifier needs
719+
/// ONE root over the whole round, which nothing blesses yet — the VM-side
720+
/// analogue of the M-8 round-coverage gap), and `multi_verify_batched` fails
721+
/// closed on that by design. This instrument measures the PROVE.
722+
#[test]
723+
#[ignore]
724+
fn the_real_block_base_epoch_ab() {
725+
use crate::tables::trace_builder::{Traces, build_initial_image_paged};
726+
use crate::tables::{MaxRowsConfig, bitwise, local_to_global, register};
727+
use executor::elf::Elf;
728+
use executor::vm::execution::Executor;
729+
use stark::prover::IsStarkProver;
730+
731+
for var in ["LFM_CENSUS_ELF", "LFM_CENSUS_INPUT"] {
732+
assert!(
733+
std::env::var(var).is_ok(),
734+
"{var} must name a file: this A/B measures a REAL block epoch"
735+
);
736+
}
737+
let mode = std::env::var("AB_MODE").expect("AB_MODE must be per_table or batched");
738+
let residency = match std::env::var("LAMBDA_VM_RESIDENCY").as_deref() {
739+
Ok("recompute") => stark::residency_mode::ResidencyMode::RecomputeLde,
740+
_ => stark::residency_mode::ResidencyMode::Retain,
741+
};
742+
743+
let EpochInputs {
744+
elf_bytes,
745+
private_input,
746+
epoch_log2,
747+
label: guest_label,
748+
} = EpochInputs::from_env();
749+
let opts = crate::recursion::Preset::Blowup4.options();
750+
let mut inner = opts;
751+
if let Ok(v) = std::env::var("LFM_WRAP_QUERIES") {
752+
inner.fri_number_of_queries = v.parse().expect("LFM_WRAP_QUERIES must be an integer");
753+
}
754+
let opts = inner;
755+
println!(
756+
"★ BASE A/B ARM: mode={mode} residency={residency:?} guest {guest_label}, \
757+
2^{epoch_log2} cycles/epoch, blowup {} / {} queries",
758+
opts.blowup_factor, opts.fri_number_of_queries,
759+
);
760+
761+
let elf = Elf::load(&elf_bytes).expect("the inner ELF must load");
762+
let epoch_size = 1usize << epoch_log2;
763+
let mut executor = Executor::new(&elf, private_input.clone()).expect("executor");
764+
let image = build_initial_image_paged(&elf, &private_input);
765+
let register_init = register::register_init_from_entry_point(elf.entry_point);
766+
let logs = executor
767+
.resume_with_limit(epoch_size)
768+
.expect("resume")
769+
.expect("the guest runs at least one epoch")
770+
.to_vec();
771+
let is_final = executor.pc() == 0;
772+
assert!(!is_final, "wanted an INTERMEDIATE epoch");
773+
774+
let mut traces = Traces::from_image_and_logs(
775+
&elf,
776+
&image,
777+
&register_init,
778+
&logs,
779+
&MaxRowsConfig::default(),
780+
&private_input,
781+
is_final,
782+
true,
783+
#[cfg(feature = "disk-spill")]
784+
stark::storage_mode::StorageMode::Ram,
785+
)
786+
.expect("the epoch trace must build");
787+
788+
let label = local_to_global::epoch_label(0);
789+
let mut provenance =
790+
local_to_global::genesis_provenance(image.iter().map(|(a, v)| (a, v as u64)));
791+
let boundary =
792+
local_to_global::epoch_boundary(&mut provenance, label, &traces.touched_memory_cells);
793+
bitwise::update_multiplicities(
794+
&mut traces.bitwise,
795+
&local_to_global::collect_bitwise_from_l2g(&boundary),
796+
);
797+
798+
let reg_fini = register::fini_from_trace(&traces.register);
799+
let table_counts = traces.table_counts();
800+
let public_output = traces.public_output_bytes.clone();
801+
let runtime_page_ranges = traces.runtime_page_ranges();
802+
803+
let airs = crate::VmAirs::new(
804+
&elf,
805+
&opts,
806+
false,
807+
&[],
808+
&table_counts,
809+
None,
810+
is_final,
811+
None,
812+
None,
813+
Some((
814+
register::compute_precomputed_commitment_with_fini(&opts, &register_init, &reg_fini),
815+
register::NUM_PREPROCESSED_COLS_WITH_FINI,
816+
)),
817+
);
818+
let l2g_air = crate::continuation::l2g_memory_air(&opts, label);
819+
let mut l2g_trace = local_to_global::generate_local_to_global_trace(&boundary);
820+
821+
let seed = || {
822+
let mut t = stark::config::DefaultStarkTranscript::<Ext3>::new(&[]);
823+
crate::statement::absorb_statement(
824+
&mut t,
825+
crate::statement::StatementKind::ContinuationEpoch { epoch_label: label },
826+
&elf_bytes,
827+
&public_output,
828+
&table_counts,
829+
0,
830+
&runtime_page_ranges,
831+
opts.fri_final_poly_log_degree,
832+
);
833+
t
834+
};
835+
836+
let mut pairs = airs.air_trace_pairs(&mut traces);
837+
pairs.push((&l2g_air, &mut l2g_trace, &()));
838+
839+
// ---- THE MEASURED PROVE. Everything above is identical shared setup.
840+
let t = std::time::Instant::now();
841+
match mode.as_str() {
842+
"per_table" => {
843+
let proof = stark::prover::Prover::<Gl, Ext3, ()>::multi_prove(
844+
pairs,
845+
&mut seed(),
846+
#[cfg(feature = "disk-spill")]
847+
stark::storage_mode::StorageMode::Ram,
848+
residency,
849+
)
850+
.expect("the epoch must prove");
851+
let prove_secs = t.elapsed().as_secs_f64();
852+
let size = rkyv::to_bytes::<rkyv::rancor::Error>(&proof)
853+
.expect("the epoch proof must serialize")
854+
.len();
855+
println!(
856+
"★ BASE A/B RESULT mode=per_table PROVE_SECS={prove_secs:.2} \
857+
SUB_PROOFS={} PROOF_BYTES={size}",
858+
proof.proofs.len(),
859+
);
860+
}
861+
"batched" => {
862+
let (proof, stats) = stark::batched::prover::multi_prove_batched::<
863+
Gl,
864+
Ext3,
865+
(),
866+
stark::config::DefaultStarkHash,
867+
stark::prover::Prover<Gl, Ext3, ()>,
868+
>(
869+
pairs,
870+
&mut seed(),
871+
None,
872+
#[cfg(feature = "disk-spill")]
873+
stark::storage_mode::StorageMode::Ram,
874+
residency,
875+
)
876+
.expect("the batched epoch must prove");
877+
let prove_secs = t.elapsed().as_secs_f64();
878+
println!(
879+
"★ BASE A/B RESULT mode=batched PROVE_SECS={prove_secs:.2} \
880+
TABLES={} QUERIES={} FRI_LAYERS={} PREP_ROOT={}",
881+
proof.tables.len(),
882+
proof.queries.len(),
883+
proof.fri_layer_roots.len(),
884+
proof.prep_root.is_some(),
885+
);
886+
println!(" BATCHED_STATS {stats:?}");
887+
}
888+
other => panic!("AB_MODE must be per_table or batched, not {other}"),
889+
}
890+
}
891+
700892
pub(super) fn real_epoch_with(opts: crate::ProofOptions) -> RealEpoch {
701893
real_epoch_from(opts, EpochInputs::from_env())
702894
}

0 commit comments

Comments
 (0)