Skip to main content

sp1_core_executor/
opts.rs

1use crate::{
2    cost_and_height_per_syscall, rv64im_costs, utils::trunc_32, RetainedEventsPreset, RiscvAirId,
3    SyscallCode, BYTE_NUM_ROWS, RANGE_NUM_ROWS,
4};
5use enum_map::EnumMap;
6use serde::{Deserialize, Serialize};
7use std::{collections::HashSet, env};
8
9const MAX_SHARD_SIZE: usize = 1 << 24;
10
11/// The trace area threshold for a shard.
12pub const ELEMENT_THRESHOLD: u64 = (1 << 28) + (1 << 27);
13/// The height threshold for a shard.
14pub const HEIGHT_THRESHOLD: u64 = 1 << 22;
15/// The maximum size of a minimal trace chunk in terms of memory entries.
16///
17/// 16,777,216 entries × 16 B/entry = 256 MiB per chunk. With
18/// [`DEFAULT_TRACE_CHUNK_SLOTS`] slots in the SHM ring buffer, this caps the
19/// trace-ring shared memory at ~5 × 256 MiB × 10/9 ≈ 1.43 GiB.
20pub const MINIMAL_TRACE_CHUNK_THRESHOLD: u64 = 16_777_216;
21/// The default number trace chunk slots
22pub const DEFAULT_TRACE_CHUNK_SLOTS: usize = 5;
23/// Default memory limit for SP1 programs, note this value has different semantics
24/// on different implementation. For native executor, it is the limit on total
25/// process memory(resident set size, or RSS) of this entire child process. For
26/// portable executor, it is merely the limit on created memory entries. This
27/// means the actual memory usage for portable executor will exceed this limit.
28pub const DEFAULT_MEMORY_LIMIT: u64 = 24 * 1024 * 1024 * 1024;
29
30/// The threshold that determines when to split the shard.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32pub struct ShardingThreshold {
33    /// The maximum number of elements in the trace.
34    pub element_threshold: u64,
35    /// The maximum number of rows for a single operation.
36    pub height_threshold: u64,
37}
38
39/// Options for the core prover.
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
41pub struct SP1CoreOpts {
42    /// The maximum size of a minimal trace chunk in terms of memory entries.
43    pub minimal_trace_chunk_threshold: u64,
44    /// The number of slots in trace chunk ring buffer.
45    pub trace_chunk_slots: usize,
46    /// The memory limit of SP1 program.
47    pub memory_limit: u64,
48    /// The size of a shard in terms of cycles. Used for estimating event counts when allocating records.
49    pub shard_size: usize,
50    /// The threshold that determines when to split the shard.
51    pub sharding_threshold: ShardingThreshold,
52    /// Preset collections of events to retain in a shard instead of deferring.
53    pub retained_events_presets: HashSet<RetainedEventsPreset>,
54    /// Use optimized `generate_dependencies` for global chip.
55    pub global_dependencies_opt: bool,
56}
57
58impl Default for SP1CoreOpts {
59    fn default() -> Self {
60        let minimal_trace_chunk_threshold = env::var("MINIMAL_TRACE_CHUNK_THRESHOLD").map_or_else(
61            |_| MINIMAL_TRACE_CHUNK_THRESHOLD,
62            |s| s.parse::<u64>().unwrap_or(MINIMAL_TRACE_CHUNK_THRESHOLD),
63        );
64
65        let trace_chunk_slots = env::var("TRACE_CHUNK_SLOTS").map_or_else(
66            |_| DEFAULT_TRACE_CHUNK_SLOTS,
67            |s| s.parse::<usize>().unwrap_or(DEFAULT_TRACE_CHUNK_SLOTS),
68        );
69
70        let memory_limit = env::var("MEMORY_LIMIT").map_or_else(
71            |_| DEFAULT_MEMORY_LIMIT,
72            |s| s.parse::<u64>().unwrap_or(DEFAULT_MEMORY_LIMIT),
73        );
74
75        let shard_size = env::var("SHARD_SIZE")
76            .map_or_else(|_| MAX_SHARD_SIZE, |s| s.parse::<usize>().unwrap_or(MAX_SHARD_SIZE));
77
78        let element_threshold = env::var("ELEMENT_THRESHOLD")
79            .map_or_else(|_| ELEMENT_THRESHOLD, |s| s.parse::<u64>().unwrap_or(ELEMENT_THRESHOLD));
80
81        let height_threshold = env::var("HEIGHT_THRESHOLD")
82            .map_or_else(|_| HEIGHT_THRESHOLD, |s| s.parse::<u64>().unwrap_or(HEIGHT_THRESHOLD));
83
84        let sharding_threshold = ShardingThreshold { element_threshold, height_threshold };
85
86        let mut retained_events_presets = HashSet::new();
87        retained_events_presets.insert(RetainedEventsPreset::Bls12381Field);
88        retained_events_presets.insert(RetainedEventsPreset::Bn254Field);
89        retained_events_presets.insert(RetainedEventsPreset::Sha256);
90        retained_events_presets.insert(RetainedEventsPreset::Poseidon2);
91        retained_events_presets.insert(RetainedEventsPreset::U256Ops);
92
93        Self {
94            minimal_trace_chunk_threshold,
95            trace_chunk_slots,
96            memory_limit,
97            shard_size,
98            sharding_threshold,
99            retained_events_presets,
100            global_dependencies_opt: false,
101        }
102    }
103}
104
105/// Options for splitting deferred events.
106#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
107pub struct SplitOpts {
108    /// The threshold for combining the memory and page prot init/finalize events in to the current
109    /// shard in terms of the estimated trace area of the shard.
110    pub pack_trace_threshold: u64,
111    /// The threshold for combining the memory init/finalize events in to the current shard in
112    /// terms of the number of memory init/finalize events.
113    pub combine_memory_threshold: usize,
114    /// The threshold for combining the page prot init/finalize events in to the current shard in
115    /// terms of the number of page prot init/finalize events.
116    pub combine_page_prot_threshold: usize,
117    /// The threshold for syscall codes.
118    pub syscall_threshold: EnumMap<SyscallCode, usize>,
119    /// The threshold for memory events.
120    pub memory: usize,
121    /// The threshold for page prot events.
122    pub page_prot: usize,
123}
124
125impl SplitOpts {
126    /// Create a new [`SplitOpts`] with the given [`SP1CoreOpts`] and the program size.
127    #[must_use]
128    pub fn new(opts: &SP1CoreOpts, program_size: usize, page_protect_allowed: bool) -> Self {
129        let costs = rv64im_costs();
130
131        let mut available_trace_area = opts.sharding_threshold.element_threshold;
132        let mut fixed_trace_area = 0;
133        fixed_trace_area += program_size.next_multiple_of(32) * costs[&RiscvAirId::Program];
134        fixed_trace_area += BYTE_NUM_ROWS as usize * costs[&RiscvAirId::Byte];
135        fixed_trace_area += RANGE_NUM_ROWS as usize * costs[&RiscvAirId::Range];
136
137        assert!(
138            available_trace_area >= fixed_trace_area as u64,
139            "SP1CoreOpts's element threshold is too low"
140        );
141
142        available_trace_area -= fixed_trace_area as u64;
143
144        let max_height = opts.sharding_threshold.height_threshold;
145
146        let syscall_threshold = EnumMap::from_fn(|syscall_code: SyscallCode| {
147            if syscall_code.should_send() == 0 || syscall_code.as_air_id().is_none() {
148                return 0;
149            }
150
151            let (cost_per_syscall, max_height_per_syscall) =
152                cost_and_height_per_syscall(syscall_code, &costs, page_protect_allowed);
153            let element_threshold = trunc_32(available_trace_area as usize / cost_per_syscall);
154            let height_threshold = trunc_32(max_height as usize / max_height_per_syscall);
155
156            element_threshold.min(height_threshold)
157        });
158
159        let cost_per_memory = costs[&RiscvAirId::MemoryGlobalInit] + costs[&RiscvAirId::Global];
160        let memory = trunc_32(
161            (available_trace_area as usize / cost_per_memory).min(max_height as usize) / 2,
162        );
163        let cost_per_page_prot =
164            costs[&RiscvAirId::PageProtGlobalInit] + costs[&RiscvAirId::Global];
165        let page_prot = trunc_32(
166            (available_trace_area as usize / cost_per_page_prot).min(max_height as usize) / 2,
167        );
168
169        // Allocate `2/3` of the trace area to the usual trace area.
170        let pack_trace_threshold = 2 * opts.sharding_threshold.element_threshold / 3;
171        // Allocate `3/10` of the trace area to `MemoryGlobal` and `PageProtGlobal`.
172        let mut combine_memory_threshold =
173            trunc_32(3 * opts.sharding_threshold.element_threshold as usize / cost_per_memory / 40);
174        let mut combine_page_prot_threshold = trunc_32(
175            3 * opts.sharding_threshold.element_threshold as usize / cost_per_page_prot / 40,
176        );
177
178        // If page protection is off, use the `3/10` of the trace area for `MemoryGlobal` only.
179        if !page_protect_allowed {
180            combine_memory_threshold *= 2;
181            combine_page_prot_threshold = 0;
182        }
183
184        Self {
185            pack_trace_threshold,
186            combine_memory_threshold,
187            combine_page_prot_threshold,
188            syscall_threshold,
189            memory,
190            page_prot,
191        }
192    }
193}