Skip to main content

eredu_runtime/cache/
telemetry.rs

1//! Backend-neutral mutable-cache residency telemetry.
2
3use std::{collections::BTreeMap, time::Duration};
4
5/// Maximum number of individually identified layers in a residency report.
6///
7/// Additional active layers are folded into
8/// [`CacheResidencyReport::per_layer_overflow`], so report size is independent
9/// of caller-provided layer identifiers and remains bounded.
10pub const CACHE_RESIDENCY_LAYER_REPORT_LIMIT: usize = 128;
11
12/// Current residency and cumulative activity attributable to one layer or a
13/// bounded overflow group of layers.
14#[derive(Debug, Clone, Default, Eq, PartialEq)]
15pub struct CacheLayerResidencyStats {
16    /// Logical cached tokens.
17    pub logical_cached_tokens: u64,
18    /// Sealed key/value blocks.
19    pub key_value_blocks: u64,
20    /// Sealed compressed-latent/rotary blocks.
21    pub compressed_latent_blocks: u64,
22    /// Blocks cataloged on the execution device.
23    pub device_blocks: u64,
24    /// Blocks cataloged in host memory.
25    pub host_blocks: u64,
26    /// Blocks cataloged on disk.
27    pub disk_blocks: u64,
28    /// Current logical device bytes, including mutable tails.
29    pub current_device_bytes: u64,
30    /// Current physical host allocation capacity, including in-flight ownership.
31    pub current_host_bytes: u64,
32    /// Current logical disk bytes.
33    pub current_disk_bytes: u64,
34    /// Current bytes in mutable tails.
35    pub mutable_tail_bytes: u64,
36    /// Blocks whose host buffers are owned by background disk writes.
37    pub in_flight_write_blocks: u64,
38    /// Physical host allocation capacity owned by background disk writes.
39    pub in_flight_write_bytes: u64,
40    /// Blocks retaining both device and host allocations during demotion.
41    pub in_flight_host_demotion_blocks: u64,
42    /// Physical host allocation capacity charged during device demotion.
43    pub in_flight_host_demotion_bytes: u64,
44    /// Recent device blocks protected from demotion.
45    pub protected_recent_blocks: u64,
46    /// Prefix or sink blocks protected for attention.
47    pub protected_prefix_blocks: u64,
48    /// Cumulative host promotions.
49    pub host_promotions: u64,
50    /// Cumulative disk promotions.
51    pub disk_promotions: u64,
52    /// Cumulative device demotions.
53    pub host_demotions: u64,
54    /// Cumulative demotions to disk.
55    pub disk_demotions: u64,
56    /// Cumulative logical bytes transferred between tiers.
57    pub transfer_bytes: u64,
58    /// Cumulative host time at transfer ownership boundaries.
59    pub transfer_wait: Duration,
60    /// Cumulative demand hits.
61    pub demand_hits: u64,
62    /// Cumulative demand misses.
63    pub demand_misses: u64,
64    /// Cumulative in-flight waits.
65    pub in_flight_waits: u64,
66    /// Cumulative residency or transfer failures.
67    pub failures: u64,
68    /// Sealed and mutable blocks scanned by full attention during prefill.
69    pub prefill_full_attention_blocks: u64,
70    /// Logical bytes scanned by full attention during prefill.
71    pub prefill_full_attention_bytes: u64,
72    /// Sealed and mutable blocks scanned by full attention during decode.
73    pub decode_full_attention_blocks: u64,
74    /// Logical bytes scanned by full attention during decode.
75    pub decode_full_attention_bytes: u64,
76    /// Peak logical scratch bytes used by this layer's attention.
77    pub attention_scratch_peak_bytes: u64,
78}
79
80impl CacheLayerResidencyStats {
81    /// Adds another exact row into this aggregate, preserving peak fields.
82    pub fn accumulate(&mut self, other: &Self) {
83        self.logical_cached_tokens += other.logical_cached_tokens;
84        self.key_value_blocks += other.key_value_blocks;
85        self.compressed_latent_blocks += other.compressed_latent_blocks;
86        self.device_blocks += other.device_blocks;
87        self.host_blocks += other.host_blocks;
88        self.disk_blocks += other.disk_blocks;
89        self.current_device_bytes += other.current_device_bytes;
90        self.current_host_bytes += other.current_host_bytes;
91        self.current_disk_bytes += other.current_disk_bytes;
92        self.mutable_tail_bytes += other.mutable_tail_bytes;
93        self.in_flight_write_blocks += other.in_flight_write_blocks;
94        self.in_flight_write_bytes += other.in_flight_write_bytes;
95        self.in_flight_host_demotion_blocks += other.in_flight_host_demotion_blocks;
96        self.in_flight_host_demotion_bytes += other.in_flight_host_demotion_bytes;
97        self.protected_recent_blocks += other.protected_recent_blocks;
98        self.protected_prefix_blocks += other.protected_prefix_blocks;
99        self.host_promotions += other.host_promotions;
100        self.disk_promotions += other.disk_promotions;
101        self.host_demotions += other.host_demotions;
102        self.disk_demotions += other.disk_demotions;
103        self.transfer_bytes += other.transfer_bytes;
104        self.transfer_wait += other.transfer_wait;
105        self.demand_hits += other.demand_hits;
106        self.demand_misses += other.demand_misses;
107        self.in_flight_waits += other.in_flight_waits;
108        self.failures += other.failures;
109        self.prefill_full_attention_blocks += other.prefill_full_attention_blocks;
110        self.prefill_full_attention_bytes += other.prefill_full_attention_bytes;
111        self.decode_full_attention_blocks += other.decode_full_attention_blocks;
112        self.decode_full_attention_bytes += other.decode_full_attention_bytes;
113        self.attention_scratch_peak_bytes = self
114            .attention_scratch_peak_bytes
115            .max(other.attention_scratch_peak_bytes);
116    }
117}
118
119/// Bounded, individually identified per-layer residency observations.
120#[derive(Debug, Clone, Default, Eq, PartialEq)]
121pub struct CacheLayerResidencyReport {
122    /// Global model layer identifier.
123    pub global_layer: usize,
124    /// Current residency and cumulative activity for this layer.
125    pub stats: CacheLayerResidencyStats,
126}
127
128/// Aggregated logical device/disk and physical host residency observations.
129#[derive(Debug, Clone, Default, Eq, PartialEq)]
130pub struct CacheResidencyReport {
131    /// Absolute token count represented by the longest layer.
132    pub logical_cached_tokens: u64,
133    /// Sealed key/value blocks.
134    pub key_value_blocks: u64,
135    /// Sealed compressed-latent/rotary blocks.
136    pub compressed_latent_blocks: u64,
137    /// Blocks cataloged on the execution device.
138    pub device_blocks: u64,
139    /// Blocks cataloged in host memory.
140    pub host_blocks: u64,
141    /// Blocks cataloged on disk.
142    pub disk_blocks: u64,
143    /// Current logical device bytes, including mutable tails.
144    pub current_device_bytes: u64,
145    /// Peak successfully admitted logical device bytes.
146    pub peak_device_bytes: u64,
147    /// Current physical host allocation capacity.
148    pub current_host_bytes: u64,
149    /// Peak successfully admitted physical host allocation capacity.
150    pub peak_host_bytes: u64,
151    /// Current logical disk bytes.
152    pub current_disk_bytes: u64,
153    /// Peak successfully admitted logical disk bytes.
154    pub peak_disk_bytes: u64,
155    /// Blocks whose host buffers are owned by background disk writes.
156    pub in_flight_write_blocks: u64,
157    /// Physical host capacity owned by disk writes.
158    pub in_flight_write_bytes: u64,
159    /// Peak physical host capacity owned by background disk writes.
160    pub peak_in_flight_write_bytes: u64,
161    /// Blocks retaining both device and host allocations during demotion.
162    pub in_flight_host_demotion_blocks: u64,
163    /// Physical host capacity charged during device demotion.
164    pub in_flight_host_demotion_bytes: u64,
165    /// Peak physical host capacity charged during device demotion.
166    pub peak_in_flight_host_demotion_bytes: u64,
167    /// Current bytes in mutable tails.
168    pub mutable_tail_bytes: u64,
169    /// Recent blocks protected from device demotion.
170    pub protected_recent_blocks: u64,
171    /// Prefix or sink blocks protected for attention.
172    pub protected_prefix_blocks: u64,
173    /// Current per-layer rows, sorted and bounded.
174    pub per_layer: Vec<CacheLayerResidencyReport>,
175    /// Number of active layers folded into the overflow row.
176    pub per_layer_overflow_layers: u64,
177    /// Exact aggregate of omitted and unidentifiable layers.
178    pub per_layer_overflow: CacheLayerResidencyStats,
179    /// Host-to-device promotions.
180    pub host_promotions: u64,
181    /// Disk-to-device promotions.
182    pub disk_promotions: u64,
183    /// Device-to-host demotions.
184    pub host_demotions: u64,
185    /// Resident-to-disk demotions.
186    pub disk_demotions: u64,
187    /// Logical bytes copied by promotions and demotions.
188    pub transfer_bytes: u64,
189    /// Host time spent at transfer ownership boundaries.
190    pub transfer_wait: Duration,
191    /// Blocks evicted after configured tiers were exhausted.
192    pub evictions: u64,
193    /// Sliding-window blocks discarded as invisible.
194    pub discarded_sliding_blocks: u64,
195    /// Completed block seals.
196    pub block_seals: u64,
197    /// Mutable tail allocations.
198    pub tail_allocations: u64,
199    /// Demand hits.
200    pub demand_hits: u64,
201    /// Demand misses.
202    pub demand_misses: u64,
203    /// Requests that joined an existing transfer.
204    pub in_flight_waits: u64,
205    /// Effective bounded disk request queue capacity.
206    pub queue_capacity: usize,
207    /// Peak observed queue occupancy.
208    pub queue_peak_occupancy: usize,
209    /// Requests delayed by queue capacity.
210    pub queue_backpressure: u64,
211    /// Requests canceled by reset or truncation.
212    pub cancellations: u64,
213    /// Cache transfer or persistence failures.
214    pub failures: u64,
215    /// Blocks scanned by full attention during prefill.
216    pub prefill_full_attention_blocks: u64,
217    /// Logical bytes scanned by full attention during prefill.
218    pub prefill_full_attention_bytes: u64,
219    /// Blocks scanned by full attention during decode.
220    pub decode_full_attention_blocks: u64,
221    /// Logical bytes scanned by full attention during decode.
222    pub decode_full_attention_bytes: u64,
223    /// Peak logical scratch bytes used by attention.
224    pub attention_scratch_peak_bytes: u64,
225    /// Successful prompt-cache saves.
226    pub prompt_cache_saves: u64,
227    /// Successful prompt-cache loads.
228    pub prompt_cache_loads: u64,
229    /// Logical bytes written or cataloged for prompt caches.
230    pub prompt_cache_bytes: u64,
231    /// Imported persistent shard count.
232    pub imported_buffered_shards: u64,
233    /// Optional peak process resident-set size.
234    pub process_rss_bytes: Option<u64>,
235    /// Optional cumulative minor page faults.
236    pub process_minor_page_faults: Option<u64>,
237    /// Optional cumulative major page faults.
238    pub process_major_page_faults: Option<u64>,
239}
240
241/// Backend-neutral collector for bounded cache activity and snapshot assembly.
242///
243/// Backends update the aggregate report while inspecting their native storage,
244/// then provide exact current per-layer totals to [`Self::finalize_snapshot`].
245/// Historical layer identities and overflow accounting remain runtime-owned.
246#[derive(Debug, Default)]
247pub struct CacheResidencyTelemetry {
248    /// Aggregate current and cumulative report fields.
249    pub report: CacheResidencyReport,
250    layer_activity: BTreeMap<usize, CacheLayerResidencyStats>,
251    layer_activity_overflow: CacheLayerResidencyStats,
252}
253
254impl CacheResidencyTelemetry {
255    /// Creates an empty collector with the effective I/O queue capacity.
256    pub fn new(queue_capacity: usize) -> Self {
257        let mut telemetry = Self::default();
258        telemetry.report.queue_capacity = queue_capacity;
259        telemetry
260    }
261
262    /// Returns cumulative activity storage for one identified layer.
263    ///
264    /// The first bounded set of layer identities remains stable for the life of
265    /// the collector. Later identities are folded into an exact overflow row.
266    pub fn layer_activity_mut(&mut self, global_layer: usize) -> &mut CacheLayerResidencyStats {
267        if self.layer_activity.contains_key(&global_layer)
268            || self.layer_activity.len() < CACHE_RESIDENCY_LAYER_REPORT_LIMIT
269        {
270            self.layer_activity.entry(global_layer).or_default()
271        } else {
272            &mut self.layer_activity_overflow
273        }
274    }
275
276    /// Returns cumulative activity storage for work without a layer identity.
277    pub fn unassigned_activity_mut(&mut self) -> &mut CacheLayerResidencyStats {
278        &mut self.layer_activity_overflow
279    }
280
281    /// Merges exact current per-layer totals with cumulative runtime activity.
282    ///
283    /// Peak aggregate fields advance only when current usage is within the
284    /// configured limit, matching successful-admission semantics.
285    pub fn finalize_snapshot(
286        &mut self,
287        mut current: BTreeMap<usize, CacheLayerResidencyStats>,
288        device_budget_bytes: u64,
289        host_budget_bytes: u64,
290        disk_budget_bytes: Option<u64>,
291    ) {
292        self.report.per_layer.clear();
293        self.report.per_layer_overflow_layers = 0;
294        self.report.per_layer_overflow = CacheLayerResidencyStats::default();
295
296        let mut selected_layers = self.layer_activity.keys().copied().collect::<Vec<_>>();
297        for global_layer in current.keys().copied() {
298            if selected_layers.len() == CACHE_RESIDENCY_LAYER_REPORT_LIMIT {
299                break;
300            }
301            if !self.layer_activity.contains_key(&global_layer) {
302                selected_layers.push(global_layer);
303            }
304        }
305        selected_layers.sort_unstable();
306        for global_layer in selected_layers {
307            let mut stats = current.remove(&global_layer).unwrap_or_default();
308            if let Some(activity) = self.layer_activity.get(&global_layer) {
309                apply_activity(activity, &mut stats);
310            }
311            self.report.per_layer.push(CacheLayerResidencyReport {
312                global_layer,
313                stats,
314            });
315        }
316        for (_, stats) in current {
317            self.report.per_layer_overflow_layers += 1;
318            self.report.per_layer_overflow.accumulate(&stats);
319        }
320        apply_activity(
321            &self.layer_activity_overflow,
322            &mut self.report.per_layer_overflow,
323        );
324
325        if self.report.current_device_bytes <= device_budget_bytes {
326            self.report.peak_device_bytes = self
327                .report
328                .peak_device_bytes
329                .max(self.report.current_device_bytes);
330        }
331        if self.report.current_host_bytes <= host_budget_bytes {
332            self.report.peak_host_bytes = self
333                .report
334                .peak_host_bytes
335                .max(self.report.current_host_bytes);
336        }
337        if disk_budget_bytes.is_none_or(|budget| self.report.current_disk_bytes <= budget) {
338            self.report.peak_disk_bytes = self
339                .report
340                .peak_disk_bytes
341                .max(self.report.current_disk_bytes);
342        }
343        self.report.peak_in_flight_write_bytes = self
344            .report
345            .peak_in_flight_write_bytes
346            .max(self.report.in_flight_write_bytes);
347        self.report.peak_in_flight_host_demotion_bytes = self
348            .report
349            .peak_in_flight_host_demotion_bytes
350            .max(self.report.in_flight_host_demotion_bytes);
351    }
352}
353
354fn apply_activity(activity: &CacheLayerResidencyStats, stats: &mut CacheLayerResidencyStats) {
355    stats.host_promotions += activity.host_promotions;
356    stats.disk_promotions += activity.disk_promotions;
357    stats.host_demotions += activity.host_demotions;
358    stats.disk_demotions += activity.disk_demotions;
359    stats.transfer_bytes += activity.transfer_bytes;
360    stats.transfer_wait += activity.transfer_wait;
361    stats.demand_hits += activity.demand_hits;
362    stats.demand_misses += activity.demand_misses;
363    stats.in_flight_waits += activity.in_flight_waits;
364    stats.failures += activity.failures;
365    stats.prefill_full_attention_blocks += activity.prefill_full_attention_blocks;
366    stats.prefill_full_attention_bytes += activity.prefill_full_attention_bytes;
367    stats.decode_full_attention_blocks += activity.decode_full_attention_blocks;
368    stats.decode_full_attention_bytes += activity.decode_full_attention_bytes;
369    stats.attention_scratch_peak_bytes = stats
370        .attention_scratch_peak_bytes
371        .max(activity.attention_scratch_peak_bytes);
372}
373
374#[cfg(test)]
375mod tests {
376    use super::*;
377
378    #[test]
379    fn aggregation_sums_current_and_cumulative_fields_but_preserves_peaks() {
380        let mut aggregate = CacheLayerResidencyStats {
381            current_device_bytes: 4,
382            transfer_bytes: 8,
383            attention_scratch_peak_bytes: 16,
384            ..Default::default()
385        };
386        aggregate.accumulate(&CacheLayerResidencyStats {
387            current_device_bytes: 5,
388            transfer_bytes: 9,
389            attention_scratch_peak_bytes: 12,
390            ..Default::default()
391        });
392        assert_eq!(aggregate.current_device_bytes, 9);
393        assert_eq!(aggregate.transfer_bytes, 17);
394        assert_eq!(aggregate.attention_scratch_peak_bytes, 16);
395    }
396
397    #[test]
398    fn telemetry_keeps_historical_layers_stable_and_folds_current_overflow() {
399        let mut telemetry = CacheResidencyTelemetry::new(3);
400        for layer in 0..CACHE_RESIDENCY_LAYER_REPORT_LIMIT {
401            telemetry.layer_activity_mut(layer).demand_hits = 1;
402        }
403        telemetry.unassigned_activity_mut().failures = 2;
404        telemetry.report.current_device_bytes = 8;
405        telemetry.report.current_host_bytes = 12;
406        telemetry.report.current_disk_bytes = 16;
407        telemetry.finalize_snapshot(
408            BTreeMap::from([(
409                CACHE_RESIDENCY_LAYER_REPORT_LIMIT,
410                CacheLayerResidencyStats {
411                    device_blocks: 1,
412                    ..Default::default()
413                },
414            )]),
415            8,
416            12,
417            Some(16),
418        );
419
420        assert_eq!(telemetry.report.queue_capacity, 3);
421        assert_eq!(
422            telemetry.report.per_layer.len(),
423            CACHE_RESIDENCY_LAYER_REPORT_LIMIT
424        );
425        assert_eq!(telemetry.report.per_layer_overflow_layers, 1);
426        assert_eq!(telemetry.report.per_layer_overflow.device_blocks, 1);
427        assert_eq!(telemetry.report.per_layer_overflow.failures, 2);
428        assert_eq!(telemetry.report.peak_device_bytes, 8);
429        assert_eq!(telemetry.report.peak_host_bytes, 12);
430        assert_eq!(telemetry.report.peak_disk_bytes, 16);
431    }
432}