ferrum_interfaces/vnext/execution/memory/
startup.rs1use super::*;
4
5impl MemoryPlan {
6 pub fn startup_peak_bytes(
14 &self,
15 context_tokens: u64,
16 active_sequences: u32,
17 step_tokens: u64,
18 ) -> Result<u64, VNextError> {
19 self.startup_workload_peak_bytes(
20 context_tokens,
21 context_tokens,
22 active_sequences,
23 step_tokens,
24 )
25 }
26
27 pub fn startup_workload_peak_bytes(
31 &self,
32 request_ceiling_tokens: u64,
33 sequence_frontier_tokens: u64,
34 active_sequences: u32,
35 step_tokens: u64,
36 ) -> Result<u64, VNextError> {
37 if request_ceiling_tokens == 0
38 || sequence_frontier_tokens == 0
39 || sequence_frontier_tokens > request_ceiling_tokens
40 || active_sequences == 0
41 || step_tokens == 0
42 {
43 return Err(invalid_plan("startup workload dimensions must be non-zero"));
44 }
45 if active_sequences > self.maximum_active_sequences {
46 return Err(invalid_plan(
47 "startup workload exceeds the compiled sequence ceiling",
48 ));
49 }
50 let request_shape = DynamicResourceShape::from_validated(1, request_ceiling_tokens, 0);
53 let sequence_shape = DynamicResourceShape::from_validated(1, sequence_frontier_tokens, 0);
54 let step_shape = DynamicResourceShape::from_validated(active_sequences, step_tokens, 0);
55 let descriptors = self
56 .dynamic_descriptors
57 .iter()
58 .map(|descriptor| (descriptor.base_resource_id.clone(), descriptor))
59 .collect::<BTreeMap<_, _>>();
60 let sealed_workspace = self
61 .reusable_execution
62 .as_ref()
63 .map(|reusable| reusable.startup_sealed_pool_workspace_bytes())
64 .transpose()?
65 .unwrap_or_default();
66
67 self.dynamic_pools
68 .iter()
69 .try_fold(self.static_bytes, |total, pool| {
70 let sequence_bytes = pool.resource_ids.iter().try_fold(0_u64, |bytes, id| {
71 let descriptor = descriptors.get(id).ok_or_else(|| {
72 invalid_plan("startup pool references a missing descriptor")
73 })?;
74 let shape = match descriptor.lifetime() {
75 AllocationLifetime::Request => request_shape,
76 AllocationLifetime::Sequence => sequence_shape,
77 _ => return Ok(bytes),
78 };
79 let per_sequence = descriptor.evaluate_request_bytes_for_shape(shape)?;
80 per_sequence
81 .checked_mul(u64::from(active_sequences))
82 .and_then(|amount| bytes.checked_add(amount))
83 .ok_or_else(|| invalid_plan("startup sequence memory overflows u64"))
84 })?;
85 let step_bytes =
86 Self::reusable_step_bytes_for_shape(pool, &descriptors, step_shape)?;
87 let invocation_bytes =
88 Self::reusable_invocation_bytes_for_shape(pool, &descriptors, step_shape)?;
89 let mut wave_bytes = step_bytes
90 .checked_add(invocation_bytes)
91 .ok_or_else(|| invalid_plan("startup execution memory overflows u64"))?;
92
93 if let Some(reusable) = &self.reusable_execution {
97 let mut covered_classes = BTreeSet::new();
98 let mut class_buckets = BTreeMap::new();
99 for resolved in reusable.buckets() {
100 let bucket = resolved.bucket();
101 if !covered_classes.contains(bucket.class_id()) {
102 class_buckets.insert(bucket.class_id(), resolved);
103 if bucket.capacity().covers(active_sequences, step_tokens, 0) {
104 covered_classes.insert(bucket.class_id());
105 }
106 }
107 }
108 for resolved in class_buckets.into_values() {
111 if let Some(budget) = resolved
112 .pool_budgets()
113 .iter()
114 .find(|budget| budget.pool_id() == pool.pool_id())
115 {
116 wave_bytes = wave_bytes.max(budget.total_bytes()?);
117 }
118 }
119 wave_bytes = wave_bytes
123 .checked_add(sealed_workspace.get(pool.pool_id()).copied().unwrap_or(0))
124 .ok_or_else(|| invalid_plan("sealed startup workspace overflows u64"))?;
125 }
126 let required = sequence_bytes
127 .checked_add(wave_bytes)
128 .ok_or_else(|| invalid_plan("startup pool memory overflows u64"))?
129 .max(pool.provisioning.minimum_resident_bytes());
130 total
131 .checked_add(required)
132 .ok_or_else(|| invalid_plan("startup plan memory overflows u64"))
133 })
134 }
135}