1#![allow(clippy::too_many_arguments)]
8mod analysis_methods;
9#[cfg(test)]
10mod tests;
11
12use crate::fixed_point_execution_plan_cache::{
13 FixedPointExecutionPlanCache, FixedPointExecutionPlanCacheStats,
14};
15use crate::fixed_point_graph::FixedPointAnalysisKind;
16use crate::fixed_point_graph::FixedPointForwardGraph;
17use crate::fixed_point_resident_cache::{
18 FixedPointResidentGraphCache, FixedPointResidentGraphCacheStats,
19};
20use crate::fixed_point_resident_frontier::FixedPointResidentFrontierScratch;
21use vyre::ResidentGraphReuseTelemetry;
22
23#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
25pub struct FixedPointResidentBatchStats {
26 pub graph_cache: FixedPointResidentGraphCacheStats,
28 pub execution_plan_cache: FixedPointExecutionPlanCacheStats,
30 pub frontier_allocations: u64,
32 pub frontier_reuses: u64,
34 pub frontier_clears: u64,
36 pub resident_dispatches: u64,
38 pub frontier_upload_bytes: u64,
40 pub result_readback_bytes: u64,
42 pub resident_graph_cold_uploads: u64,
44 pub resident_graph_warm_reuses: u64,
46 pub resident_graph_upload_bytes: u64,
48 pub resident_graph_avoided_upload_bytes: u64,
50 pub frontier_resident: bool,
52 pub frontier_bytes: usize,
54}
55
56impl FixedPointResidentBatchStats {
57 #[must_use]
59 pub const fn resident_graph_reuse_telemetry(self) -> ResidentGraphReuseTelemetry {
60 ResidentGraphReuseTelemetry::from_counters(
61 self.resident_graph_cold_uploads,
62 self.resident_graph_warm_reuses,
63 self.resident_graph_upload_bytes,
64 self.resident_graph_avoided_upload_bytes,
65 )
66 }
67}
68
69#[derive(Clone, Debug, PartialEq)]
71pub struct FixedPointResidentBatch {
72 graph_cache: FixedPointResidentGraphCache,
73 execution_plan_cache: FixedPointExecutionPlanCache,
74 resident_frontier: FixedPointResidentFrontierScratch,
75 host_scratch: crate::fixed_point_scratch::FixedPointScratch,
76 max_frontier_bytes: Option<usize>,
77 frontier_allocations: u64,
78 frontier_reuses: u64,
79 frontier_clears: u64,
80 resident_dispatches: u64,
81 frontier_upload_bytes: u64,
82 result_readback_bytes: u64,
83 resident_graph_reuse: ResidentGraphReuseTelemetry,
84 observed_graph_cache_reuse: ResidentGraphReuseTelemetry,
85}
86
87impl Default for FixedPointResidentBatch {
88 fn default() -> Self {
89 Self {
90 graph_cache: FixedPointResidentGraphCache::new(),
91 execution_plan_cache: FixedPointExecutionPlanCache::new(),
92 resident_frontier: FixedPointResidentFrontierScratch::default(),
93 host_scratch: crate::fixed_point_scratch::FixedPointScratch::default(),
94 max_frontier_bytes: None,
95 frontier_allocations: 0,
96 frontier_reuses: 0,
97 frontier_clears: 0,
98 resident_dispatches: 0,
99 frontier_upload_bytes: 0,
100 result_readback_bytes: 0,
101 resident_graph_reuse: ResidentGraphReuseTelemetry::default(),
102 observed_graph_cache_reuse: ResidentGraphReuseTelemetry::default(),
103 }
104 }
105}
106
107impl FixedPointResidentBatch {
108 #[must_use]
110 pub fn new() -> Self {
111 Self::default()
112 }
113
114 #[must_use]
116 pub fn with_max_retained_bytes(max_retained_bytes: usize) -> Self {
117 Self {
118 graph_cache: FixedPointResidentGraphCache::with_max_retained_bytes(max_retained_bytes),
119 ..Self::default()
120 }
121 }
122
123 #[must_use]
126 pub fn with_cache_bounds(max_retained_bytes: usize, max_execution_plans: usize) -> Self {
127 Self {
128 graph_cache: FixedPointResidentGraphCache::with_max_retained_bytes(max_retained_bytes),
129 execution_plan_cache: FixedPointExecutionPlanCache::with_max_entries(
130 max_execution_plans,
131 ),
132 ..Self::default()
133 }
134 }
135
136 #[must_use]
139 pub fn with_memory_budget(
140 max_retained_bytes: usize,
141 max_execution_plans: usize,
142 max_frontier_bytes: usize,
143 ) -> Self {
144 Self {
145 graph_cache: FixedPointResidentGraphCache::with_max_retained_bytes(max_retained_bytes),
146 execution_plan_cache: FixedPointExecutionPlanCache::with_max_entries(
147 max_execution_plans,
148 ),
149 max_frontier_bytes: Some(max_frontier_bytes),
150 ..Self::default()
151 }
152 }
153
154 #[must_use]
156 pub fn graph_cache(&self) -> &FixedPointResidentGraphCache {
157 &self.graph_cache
158 }
159
160 #[must_use]
162 pub fn graph_cache_stats(&self) -> FixedPointResidentGraphCacheStats {
163 self.graph_cache.stats()
164 }
165
166 #[must_use]
168 pub fn execution_plan_cache_stats(&self) -> FixedPointExecutionPlanCacheStats {
169 self.execution_plan_cache.stats()
170 }
171
172 #[must_use]
174 pub fn stats(&self) -> FixedPointResidentBatchStats {
175 FixedPointResidentBatchStats {
176 graph_cache: self.graph_cache.stats(),
177 execution_plan_cache: self.execution_plan_cache.stats(),
178 frontier_allocations: self.frontier_allocations,
179 frontier_reuses: self.frontier_reuses,
180 frontier_clears: self.frontier_clears,
181 resident_dispatches: self.resident_dispatches,
182 frontier_upload_bytes: self.frontier_upload_bytes,
183 result_readback_bytes: self.result_readback_bytes,
184 resident_graph_cold_uploads: self.resident_graph_reuse.cold_uploads,
185 resident_graph_warm_reuses: self.resident_graph_reuse.warm_reuses,
186 resident_graph_upload_bytes: self.resident_graph_reuse.upload_bytes,
187 resident_graph_avoided_upload_bytes: self.resident_graph_reuse.avoided_upload_bytes,
188 frontier_resident: self.resident_frontier.is_allocated(),
189 frontier_bytes: self.resident_frontier.byte_len(),
190 }
191 }
192
193 #[must_use]
195 pub fn frontier_density(&self) -> crate::fixed_point_scratch::FrontierDensityTelemetry {
196 self.host_scratch.frontier_density()
197 }
198
199 #[must_use]
201 pub fn recommended_frontier_execution_mode(
202 &self,
203 ) -> crate::fixed_point_scratch::FrontierExecutionMode {
204 self.host_scratch
205 .frontier_density()
206 .recommended_execution_mode()
207 }
208
209 pub fn execution_plan_for_prepared_graph(
212 &self,
213 graph: &FixedPointForwardGraph,
214 ) -> Result<crate::fixed_point_execution_plan::FixedPointExecutionPlan, String> {
215 crate::fixed_point_execution_plan::plan_prepared_graph(graph, self.frontier_density())
216 }
217
218 pub fn cached_execution_plan_for_prepared_graph(
221 &mut self,
222 backend: &dyn vyre::VyreBackend,
223 kind: FixedPointAnalysisKind,
224 graph: &FixedPointForwardGraph,
225 ) -> Result<crate::fixed_point_execution_plan::FixedPointExecutionPlan, String> {
226 self.execution_plan_cache
227 .get_or_plan(backend, kind, graph, self.frontier_density())
228 }
229 pub fn free_all(&mut self, backend: &dyn vyre::VyreBackend) -> Result<(), vyre::BackendError> {
231 let frontier_result = if self.resident_frontier.is_allocated() {
232 let result = self.resident_frontier.clear(backend);
233 if result.is_ok() {
234 self.frontier_clears = self
235 .frontier_clears
236 .checked_add(1)
237 .ok_or_else(|| {
238 vyre::BackendError::new(
239 "weir fixed-point resident batch frontier clear counter overflowed u64. Fix: rotate the batch facade before clearing more resident frontiers.",
240 )
241 })?;
242 }
243 result
244 } else {
245 Ok(())
246 };
247 let cache_result = self.graph_cache.free_all(backend);
248 match (frontier_result, cache_result) {
249 (Ok(()), Ok(())) => Ok(()),
250 (Err(error), _) | (_, Err(error)) => Err(error),
251 }
252 }
253
254 fn frontier_state(&self) -> (bool, usize) {
255 (
256 self.resident_frontier.is_allocated(),
257 self.resident_frontier.byte_len(),
258 )
259 }
260
261 fn record_frontier_reuse(&mut self, before: (bool, usize)) -> Result<(), String> {
262 let after = self.frontier_state();
263 if !after.0 {
264 return Ok(());
265 }
266 if before.0 && before.1 == after.1 {
267 self.frontier_reuses = self
268 .frontier_reuses
269 .checked_add(1)
270 .ok_or_else(|| {
271 "weir fixed-point resident batch frontier reuse counter overflowed u64. Fix: rotate the batch facade before reusing more resident frontiers."
272 .to_string()
273 })?;
274 } else {
275 self.frontier_allocations = self
276 .frontier_allocations
277 .checked_add(1)
278 .ok_or_else(|| {
279 "weir fixed-point resident batch frontier allocation counter overflowed u64. Fix: rotate the batch facade before allocating more resident frontiers."
280 .to_string()
281 })?;
282 }
283 Ok(())
284 }
285
286 fn record_execution_plan(
287 &mut self,
288 backend: &dyn vyre::VyreBackend,
289 kind: FixedPointAnalysisKind,
290 graph: &FixedPointForwardGraph,
291 ) -> Result<(), String> {
292 let _ =
293 self.execution_plan_cache
294 .get_or_plan(backend, kind, graph, self.frontier_density())?;
295 self.record_graph_residency_from_cache_stats()?;
296 Ok(())
297 }
298
299 fn record_graph_residency_from_cache_stats(&mut self) -> Result<(), String> {
300 let graph_cache_reuse = self.graph_cache.stats().resident_graph_reuse_telemetry();
301 let graph_cache_delta = graph_cache_reuse
302 .checked_delta_since(self.observed_graph_cache_reuse)
303 .map_err(|error| {
304 format!(
305 "weir fixed-point resident batch observed graph cache telemetry regressed: {error}"
306 )
307 })?;
308 self.resident_graph_reuse = self
309 .resident_graph_reuse
310 .checked_add(graph_cache_delta)
311 .map_err(|error| error.to_string())?;
312 self.observed_graph_cache_reuse = graph_cache_reuse;
313 Ok(())
314 }
315
316 fn record_resident_dispatch_io(
317 &mut self,
318 seed_words: usize,
319 result_words: usize,
320 ) -> Result<(), String> {
321 self.resident_dispatches = self
322 .resident_dispatches
323 .checked_add(1)
324 .ok_or_else(|| {
325 "weir fixed-point resident batch dispatch counter overflowed u64. Fix: rotate the batch facade before dispatch accounting saturates."
326 .to_string()
327 })?;
328 let logical_seed_bytes = seed_words
329 .checked_mul(std::mem::size_of::<u32>())
330 .ok_or_else(|| {
331 "weir fixed-point resident seed byte count overflowed usize. Fix: shard the seed frontier before resident dispatch."
332 .to_string()
333 })?;
334 let seed_bytes = logical_seed_bytes.checked_mul(2).ok_or_else(|| {
335 "weir fixed-point resident physical seed byte count overflowed usize. Fix: shard the seed frontier before resident dispatch."
336 .to_string()
337 })?;
338 let seed_bytes_u64 = u64::try_from(seed_bytes).map_err(|error| {
339 format!(
340 "weir fixed-point resident seed byte count does not fit u64: {error}. Fix: shard the seed frontier before resident dispatch."
341 )
342 })?;
343 self.frontier_upload_bytes = self
344 .frontier_upload_bytes
345 .checked_add(seed_bytes_u64)
346 .ok_or_else(|| {
347 "weir fixed-point resident frontier upload byte counter overflowed u64. Fix: rotate the batch facade or shard the workload."
348 .to_string()
349 })?;
350 let result_bytes = result_words
351 .checked_mul(std::mem::size_of::<u32>())
352 .ok_or_else(|| {
353 "weir fixed-point resident result byte count overflowed usize. Fix: shard the result frontier before resident readback."
354 .to_string()
355 })?;
356 let result_bytes_u64 = u64::try_from(result_bytes).map_err(|error| {
357 format!(
358 "weir fixed-point resident result byte count does not fit u64: {error}. Fix: shard the result frontier before resident readback."
359 )
360 })?;
361 self.result_readback_bytes = self
362 .result_readback_bytes
363 .checked_add(result_bytes_u64)
364 .ok_or_else(|| {
365 "weir fixed-point resident result readback byte counter overflowed u64. Fix: rotate the batch facade or shard the workload."
366 .to_string()
367 })?;
368 Ok(())
369 }
370
371 fn record_resident_changed_flag_sequence_window_io(
372 &mut self,
373 seed_words: usize,
374 result_words: usize,
375 ) -> Result<(), String> {
376 let readback_words = result_words.checked_add(1).ok_or_else(|| {
377 "weir fixed-point resident changed-flag sequence-window readback word count overflowed usize. Fix: shard the result frontier before resident readback."
378 .to_string()
379 })?;
380 self.record_resident_dispatch_io(seed_words, readback_words)
381 }
382
383 fn require_frontier_budget(&self, graph: &FixedPointForwardGraph) -> Result<(), String> {
384 let Some(max_frontier_bytes) = self.max_frontier_bytes else {
385 return Ok(());
386 };
387 let node_count = usize::try_from(graph.node_count()).map_err(|error| {
388 format!(
389 "weir resident fixed-point frontier budget cannot represent node_count={} as usize: {error}. Fix: shard the graph before resident dispatch.",
390 graph.node_count()
391 )
392 })?;
393 let frontier_bytes = node_count
394 .checked_add(31)
395 .and_then(|bits| bits.checked_div(32))
396 .and_then(|words| words.checked_mul(std::mem::size_of::<u32>()))
397 .ok_or_else(|| {
398 format!(
399 "weir resident fixed-point frontier budget overflowed for node_count={}. Fix: shard the graph before resident dispatch.",
400 graph.node_count()
401 )
402 })?;
403 if frontier_bytes > max_frontier_bytes {
404 return Err(format!(
405 "weir resident fixed-point frontier requires {frontier_bytes} bytes but the configured device-memory budget allows {max_frontier_bytes}. Fix: increase max_frontier_bytes, reuse a smaller graph shard, or lower the analysis batch size before dispatch."
406 ));
407 }
408 Ok(())
409 }
410}