@@ -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+
700892pub ( super ) fn real_epoch_with ( opts : crate :: ProofOptions ) -> RealEpoch {
701893 real_epoch_from ( opts, EpochInputs :: from_env ( ) )
702894}
0 commit comments