Skip to content

Commit bb1a23d

Browse files
authored
[Fix] metered execution suspend before guest program completed (#246)
* better guest exec * lint * update according to reviews
1 parent cd1e0ac commit bb1a23d

3 files changed

Lines changed: 92 additions & 21 deletions

File tree

crates/integration/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,8 +357,14 @@ pub fn tester_execute<T: ProverTester>(
357357
.map(|p| p.as_stark_proof().expect("must be stark proof")),
358358
)?;
359359

360+
let app_vm_config = app_config.app_vm_config.clone();
360361
let sdk = Sdk::new(app_config)?;
361-
let ret = scroll_zkvm_prover::utils::vm::execute_guest(&sdk, app_exe, &stdin)?;
362+
let ret = scroll_zkvm_prover::utils::vm::execute_guest(
363+
&sdk,
364+
app_vm_config.as_ref(),
365+
app_exe,
366+
&stdin,
367+
)?;
362368
Ok(ret)
363369
}
364370

crates/prover/src/prover/mod.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@ use openvm_native_circuit::NativeGpuBuilder as NativeBuilder;
1010

1111
use openvm_circuit::arch::instructions::exe::VmExe;
1212
use openvm_sdk::{DefaultStarkEngine, config::SdkVmBuilder};
13-
use openvm_sdk::{F, Sdk, StdIn, prover::StarkProver};
13+
use openvm_sdk::{
14+
F, Sdk, StdIn,
15+
config::{AppConfig, SdkVmConfig},
16+
prover::StarkProver,
17+
};
1418
use scroll_zkvm_types::{proof::OpenVmEvmProof, types_agg::ProgramCommitment, utils::serialize_vk};
1519
use scroll_zkvm_verifier::verifier::{AGG_STARK_PROVING_KEY, UniversalVerifier};
1620
use tracing::instrument;
1721

22+
type SdkAppConfig = AppConfig<SdkVmConfig>;
23+
1824
// Re-export from openvm_sdk.
1925
pub use openvm_sdk::{self};
2026

@@ -31,6 +37,8 @@ pub struct Prover {
3137
pub app_exe: Arc<VmExe<F>>,
3238
/// Prover configuration.
3339
pub config: ProverConfig,
40+
/// SDKConfig
41+
app_config: SdkAppConfig,
3442
/// Lazily initialized SDK
3543
sdk: OnceLock<Sdk>,
3644
/// Lazily initialized stark prover
@@ -54,11 +62,18 @@ impl Prover {
5462
/// Setup the [`Prover`] given paths to the application's exe and proving key.
5563
#[instrument("Prover::setup")]
5664
pub fn setup(config: ProverConfig, name: Option<&str>) -> Result<Self, Error> {
65+
let mut app_config = read_app_config(&config.path_app_config)?;
66+
let segment_len = config.segment_len.unwrap_or(DEFAULT_SEGMENT_SIZE);
67+
let segmentation_limits = &mut app_config.app_vm_config.system.config.segmentation_limits;
68+
segmentation_limits.max_trace_height = segment_len as u32;
69+
segmentation_limits.max_cells = 1_200_000_000_usize; // For 24G vram
70+
5771
let app_exe = read_app_exe(&config.path_app_exe)?;
5872
Ok(Self {
5973
app_exe: Arc::new(app_exe),
6074
config,
6175
prover_name: name.unwrap_or("universal").to_string(),
76+
app_config,
6277
sdk: OnceLock::new(),
6378
prover: OnceLock::new(),
6479
})
@@ -74,14 +89,7 @@ impl Prover {
7489
fn get_sdk(&self) -> Result<&Sdk, Error> {
7590
self.sdk.get_or_try_init(|| {
7691
tracing::info!("Lazy initializing SDK...");
77-
let mut app_config = read_app_config(&self.config.path_app_config)?;
78-
let segment_len = self.config.segment_len.unwrap_or(DEFAULT_SEGMENT_SIZE);
79-
let segmentation_limits =
80-
&mut app_config.app_vm_config.system.config.segmentation_limits;
81-
segmentation_limits.max_trace_height = segment_len as u32;
82-
segmentation_limits.max_cells = 1_200_000_000_usize; // For 24G vram
83-
84-
let sdk = Sdk::new(app_config).expect("sdk init failed");
92+
let sdk = Sdk::new(self.app_config.clone()).expect("sdk init failed");
8593

8694
// 45s for first time
8795
let sdk = sdk.with_agg_pk(AGG_STARK_PROVING_KEY.clone());
@@ -157,7 +165,13 @@ impl Prover {
157165
) -> Result<crate::utils::vm::ExecutionResult, Error> {
158166
let sdk = self.get_sdk()?;
159167
let t = std::time::Instant::now();
160-
let exec_result = crate::utils::vm::execute_guest(sdk, self.app_exe.clone(), stdin)?;
168+
let exec_result = crate::utils::vm::execute_guest(
169+
sdk,
170+
self.app_config.app_vm_config.as_ref(),
171+
self.app_exe.clone(),
172+
stdin,
173+
)
174+
.map_err(|e| Error::GenProof(e.to_string()))?;
161175
let execution_time_mills = t.elapsed().as_millis() as u64;
162176
let execution_time_s = execution_time_mills as f32 / 1000.0f32;
163177
let exec_speed = (exec_result.total_cycle as f32 / 1_000_000.0f32) / execution_time_s; // MHz

crates/prover/src/utils/vm.rs

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,78 @@
1-
use openvm_sdk::{Sdk, StdIn, types::ExecutableFormat};
2-
3-
use crate::Error;
1+
use openvm_circuit::{
2+
arch::{SystemConfig, VirtualMachineError},
3+
system::memory::merkle::public_values::extract_public_values,
4+
};
5+
use openvm_sdk::{Sdk, SdkError, StdIn, types::ExecutableFormat};
46

57
pub struct ExecutionResult {
68
pub total_cycle: u64,
79
#[allow(dead_code)]
810
pub public_values: Vec<u8>,
911
}
1012

13+
// the 100* current cost / ratio for chunk circuit, use to estimated the max cycles
14+
// which an metered execution can handle
15+
const COST_CYCLE_RATIO: u64 = 87u64;
16+
17+
// Execute the guest program using the metered executor first to measure actual cycles.
18+
// If the execution exceeds the maximum cost allowed by the metered executor,
19+
// we re-execute the program using the normal executor (execute_e1), which has no limitations
20+
// on the size of the execution process.
1121
pub fn execute_guest(
1222
sdk: &Sdk,
23+
sys_config: &SystemConfig,
1324
exe: impl Into<ExecutableFormat>,
14-
stdin: &StdIn,
15-
) -> Result<ExecutionResult, Error> {
16-
let (public_values, (_cost, total_cycle)) = sdk
17-
.execute_metered_cost(exe, stdin.clone())
18-
.map_err(|e| Error::GenProof(e.to_string()))?;
25+
inputs: &StdIn,
26+
) -> Result<ExecutionResult, SdkError> {
27+
let app_prover = sdk.app_prover(exe)?;
28+
29+
let vm = app_prover.vm();
30+
let exe = app_prover.exe();
31+
32+
let ctx = vm.build_metered_cost_ctx();
33+
let preset_max_cost = ctx.max_execution_cost * 2;
34+
let estimated_max_cycles = preset_max_cost / COST_CYCLE_RATIO;
35+
tracing::info!("Double preset max cost to ({preset_max_cost}) for metering execution");
36+
let ctx = ctx.with_max_execution_cost(preset_max_cost);
37+
let interpreter = vm
38+
.metered_cost_interpreter(&exe)
39+
.map_err(VirtualMachineError::from)?;
40+
41+
let (ctx, final_state) = interpreter
42+
.execute_metered_cost(inputs.clone(), ctx)
43+
.map_err(VirtualMachineError::from)?;
44+
let mut total_cycle = ctx.instret;
45+
46+
let mut public_values =
47+
extract_public_values(sys_config.num_public_values, &final_state.memory.memory);
1948

20-
tracing::debug!(name: "public_values after guest execution", ?public_values);
2149
if public_values.iter().all(|x| *x == 0) {
22-
return Err(Error::GenProof("public_values are all 0s".to_string()));
50+
if ctx.cost < ctx.max_execution_cost {
51+
return Err(SdkError::Other(eyre::eyre!(
52+
"public_values are all 0s for unexpected reason"
53+
)));
54+
}
55+
56+
tracing::warn!(
57+
"Large execution exceed limit of metered execution, cycle is expected to >{estimated_max_cycles}"
58+
);
59+
let exe = sdk.convert_to_exe(exe)?;
60+
let instance = vm.interpreter(&exe).map_err(VirtualMachineError::from)?;
61+
let final_memory = instance
62+
.execute(inputs.clone(), None)
63+
.map_err(VirtualMachineError::from)?
64+
.memory;
65+
public_values = extract_public_values(sys_config.num_public_values, &final_memory.memory);
66+
total_cycle = estimated_max_cycles;
67+
68+
if public_values.iter().all(|x| *x == 0) {
69+
return Err(SdkError::Other(eyre::eyre!(
70+
"public_values are all 0s upon execute_e1"
71+
)));
72+
}
2373
}
2474

75+
tracing::debug!(name: "public_values after guest execution: {:?}", ?public_values);
2576
Ok(ExecutionResult {
2677
total_cycle,
2778
public_values,

0 commit comments

Comments
 (0)