Skip to main content

eredu_runtime/
dense.rs

1//! Backend-neutral dense-stream residency telemetry.
2
3use eredu_core::residency::{
4    BackgroundPrefetchReport, MemoryTier, OffloadReport, OffloadUnitId, ResidencyPolicy,
5    TransferDirection, UnitResidencyReport,
6};
7use std::{
8    collections::{BTreeMap, BTreeSet},
9    sync::Mutex,
10};
11
12use crate::{ResidencyReport, WeightMaterializationReport};
13
14/// Stable dense-stream observations combining residency and worker state.
15#[derive(Debug, Clone, Eq, PartialEq)]
16pub struct DenseDiskStreamReport {
17    planned_layer_count: usize,
18    planned_layer_bytes: u64,
19    maximum_host_layer_bytes: u64,
20    pinned_static_device_bytes: u64,
21    transfer_stream_index: i32,
22    residency: ResidencyReport,
23    background: BackgroundPrefetchReport,
24    host_layers: DenseTierResidencyReport,
25    device_layers: DenseTierResidencyReport,
26    groups: Vec<DenseExecutionGroupReport>,
27    prefill: DensePassReport,
28    decode: DensePassReport,
29}
30
31impl DenseDiskStreamReport {
32    /// Creates a complete dense-stream report from a coherent residency snapshot.
33    #[allow(clippy::too_many_arguments)]
34    pub fn new(
35        planned_layer_count: usize,
36        planned_layer_bytes: u64,
37        maximum_host_layer_bytes: u64,
38        pinned_static_device_bytes: u64,
39        transfer_stream_index: i32,
40        residency: ResidencyReport,
41        background: BackgroundPrefetchReport,
42        host_layers: DenseTierResidencyReport,
43        device_layers: DenseTierResidencyReport,
44        groups: Vec<DenseExecutionGroupReport>,
45        prefill: DensePassReport,
46        decode: DensePassReport,
47    ) -> Self {
48        Self {
49            planned_layer_count,
50            planned_layer_bytes,
51            maximum_host_layer_bytes,
52            pinned_static_device_bytes,
53            transfer_stream_index,
54            residency,
55            background,
56            host_layers,
57            device_layers,
58            groups,
59            prefill,
60            decode,
61        }
62    }
63
64    /// Attaches bounded load-time materialization telemetry.
65    pub fn with_materialization(
66        mut self,
67        materialization: Option<WeightMaterializationReport>,
68    ) -> Self {
69        self.residency = self.residency.with_materialization(materialization);
70        self
71    }
72
73    /// Returns the number of disk-planned execution units.
74    pub const fn planned_layer_count(&self) -> usize {
75        self.planned_layer_count
76    }
77    /// Returns the logical checkpoint bytes in disk-planned execution units.
78    pub const fn planned_layer_bytes(&self) -> u64 {
79        self.planned_layer_bytes
80    }
81    /// Returns the charged host-transfer capacity of the largest execution unit.
82    pub const fn maximum_host_layer_bytes(&self) -> u64 {
83        self.maximum_host_layer_bytes
84    }
85    /// Returns pinned static parameter bytes outside the streamed-layer totals.
86    pub const fn pinned_static_device_bytes(&self) -> u64 {
87        self.pinned_static_device_bytes
88    }
89    /// Returns the distinct backend stream used for device weight transfers.
90    pub const fn transfer_stream_index(&self) -> i32 {
91        self.transfer_stream_index
92    }
93    /// Returns the complete logical tier and checkpoint-store report.
94    pub const fn residency(&self) -> &ResidencyReport {
95        &self.residency
96    }
97    /// Returns bounded background worker observations.
98    pub const fn background(&self) -> BackgroundPrefetchReport {
99        self.background
100    }
101    /// Returns streamed host-layer occupancy and cache history.
102    pub const fn host_layers(&self) -> DenseTierResidencyReport {
103        self.host_layers
104    }
105    /// Returns streamed device-layer occupancy and cache history.
106    pub const fn device_layers(&self) -> DenseTierResidencyReport {
107        self.device_layers
108    }
109    /// Returns point-in-time observations for each named execution group.
110    pub fn execution_groups(&self) -> &[DenseExecutionGroupReport] {
111        &self.groups
112    }
113    /// Returns completed prefill activity.
114    pub const fn prefill(&self) -> DensePassReport {
115        self.prefill
116    }
117    /// Returns completed decode activity.
118    pub const fn decode(&self) -> DensePassReport {
119        self.decode
120    }
121    /// Returns completed multi-token forward passes.
122    pub const fn prefill_forwards(&self) -> u64 {
123        self.prefill.forwards
124    }
125    /// Returns completed single-token forward passes.
126    pub const fn decode_forwards(&self) -> u64 {
127        self.decode.forwards
128    }
129}
130
131/// Cache activity attributed to one logical residency tier.
132#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
133pub struct DenseCacheMetrics {
134    requests: u64,
135    hits: u64,
136    misses: u64,
137    evictions: u64,
138    evicted_bytes: u64,
139}
140
141impl DenseCacheMetrics {
142    /// Derives cache counters from one offload snapshot and tier.
143    pub fn from_report(report: &OffloadReport, tier: MemoryTier) -> Self {
144        let prefetch = report.tier_prefetch(tier);
145        let evictions = report.tier_evictions(tier);
146        Self {
147            requests: prefetch.requests(),
148            hits: prefetch.hits(),
149            misses: prefetch.misses(),
150            evictions: evictions.count(),
151            evicted_bytes: evictions.bytes(),
152        }
153    }
154
155    /// Returns cache requests targeting the tier.
156    pub const fn requests(self) -> u64 {
157        self.requests
158    }
159    /// Returns requests served by an existing tier copy.
160    pub const fn hits(self) -> u64 {
161        self.hits
162    }
163    /// Returns requests requiring tier materialization.
164    pub const fn misses(self) -> u64 {
165        self.misses
166    }
167    /// Returns copies evicted from the tier.
168    pub const fn evictions(self) -> u64 {
169        self.evictions
170    }
171    /// Returns logical bytes evicted from the tier.
172    pub const fn evicted_bytes(self) -> u64 {
173        self.evicted_bytes
174    }
175
176    fn saturating_delta(self, earlier: Self) -> Self {
177        Self {
178            requests: self.requests.saturating_sub(earlier.requests),
179            hits: self.hits.saturating_sub(earlier.hits),
180            misses: self.misses.saturating_sub(earlier.misses),
181            evictions: self.evictions.saturating_sub(earlier.evictions),
182            evicted_bytes: self.evicted_bytes.saturating_sub(earlier.evicted_bytes),
183        }
184    }
185
186    fn saturating_add(&mut self, other: Self) {
187        self.requests = self.requests.saturating_add(other.requests);
188        self.hits = self.hits.saturating_add(other.hits);
189        self.misses = self.misses.saturating_add(other.misses);
190        self.evictions = self.evictions.saturating_add(other.evictions);
191        self.evicted_bytes = self.evicted_bytes.saturating_add(other.evicted_bytes);
192    }
193}
194
195/// Streamed-layer occupancy and cache history for one tier.
196#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
197pub struct DenseTierResidencyReport {
198    current_layer_count: usize,
199    peak_layer_count: usize,
200    current_layer_bytes: u64,
201    peak_layer_bytes: u64,
202    cache: DenseCacheMetrics,
203}
204
205impl DenseTierResidencyReport {
206    /// Creates one logical tier occupancy snapshot.
207    pub const fn new(
208        current_layer_count: usize,
209        peak_layer_count: usize,
210        current_layer_bytes: u64,
211        peak_layer_bytes: u64,
212        cache: DenseCacheMetrics,
213    ) -> Self {
214        Self {
215            current_layer_count,
216            peak_layer_count,
217            current_layer_bytes,
218            peak_layer_bytes,
219            cache,
220        }
221    }
222
223    /// Returns currently resident streamed layers.
224    pub const fn current_layer_count(self) -> usize {
225        self.current_layer_count
226    }
227    /// Returns the peak number of simultaneously resident streamed layers.
228    pub const fn peak_layer_count(self) -> usize {
229        self.peak_layer_count
230    }
231    /// Returns current streamed-layer bytes in the tier.
232    pub const fn current_layer_bytes(self) -> u64 {
233        self.current_layer_bytes
234    }
235    /// Returns peak streamed-layer bytes in the tier.
236    pub const fn peak_layer_bytes(self) -> u64 {
237        self.peak_layer_bytes
238    }
239    /// Returns cumulative cache activity for the tier.
240    pub const fn cache(self) -> DenseCacheMetrics {
241        self.cache
242    }
243}
244
245/// Point-in-time occupancy for one named execution stack.
246#[derive(Debug, Clone, Eq, PartialEq)]
247pub struct DenseExecutionGroupReport {
248    id: String,
249    planned_layers: usize,
250    planned_bytes: u64,
251    completed_executions: u64,
252    host_layers: usize,
253    host_bytes: u64,
254    peak_host_layers: usize,
255    peak_host_bytes: u64,
256    device_layers: usize,
257    device_bytes: u64,
258    peak_device_layers: usize,
259    peak_device_bytes: u64,
260}
261
262impl DenseExecutionGroupReport {
263    /// Creates one named execution-group report.
264    #[allow(clippy::too_many_arguments)]
265    pub fn new(
266        id: impl Into<String>,
267        planned_layers: usize,
268        planned_bytes: u64,
269        completed_executions: u64,
270        host_layers: usize,
271        host_bytes: u64,
272        peak_host_layers: usize,
273        peak_host_bytes: u64,
274        device_layers: usize,
275        device_bytes: u64,
276        peak_device_layers: usize,
277        peak_device_bytes: u64,
278    ) -> Self {
279        Self {
280            id: id.into(),
281            planned_layers,
282            planned_bytes,
283            completed_executions,
284            host_layers,
285            host_bytes,
286            peak_host_layers,
287            peak_host_bytes,
288            device_layers,
289            device_bytes,
290            peak_device_layers,
291            peak_device_bytes,
292        }
293    }
294
295    /// Returns the stable execution-group identifier.
296    pub fn id(&self) -> &str {
297        &self.id
298    }
299    /// Returns disk-planned layers in the group.
300    pub const fn planned_layers(&self) -> usize {
301        self.planned_layers
302    }
303    /// Returns logical checkpoint bytes in the group.
304    pub const fn planned_bytes(&self) -> u64 {
305        self.planned_bytes
306    }
307    /// Returns successfully completed executions of this group.
308    pub const fn completed_executions(&self) -> u64 {
309        self.completed_executions
310    }
311    /// Returns current host-resident group layers.
312    pub const fn host_layers(&self) -> usize {
313        self.host_layers
314    }
315    /// Returns current host-resident group bytes.
316    pub const fn host_bytes(&self) -> u64 {
317        self.host_bytes
318    }
319    /// Returns the peak number of host-resident layers observed for the group.
320    pub const fn peak_host_layers(&self) -> usize {
321        self.peak_host_layers
322    }
323    /// Returns peak host-resident layer bytes observed for the group.
324    pub const fn peak_host_bytes(&self) -> u64 {
325        self.peak_host_bytes
326    }
327    /// Returns current device-resident group layers.
328    pub const fn device_layers(&self) -> usize {
329        self.device_layers
330    }
331    /// Returns current device-resident group bytes.
332    pub const fn device_bytes(&self) -> u64 {
333        self.device_bytes
334    }
335    /// Returns the peak number of device-resident layers observed for the group.
336    pub const fn peak_device_layers(&self) -> usize {
337        self.peak_device_layers
338    }
339    /// Returns peak device-resident layer bytes observed for the group.
340    pub const fn peak_device_bytes(&self) -> u64 {
341        self.peak_device_bytes
342    }
343}
344
345/// Cache and logical transfer activity from completed prefill or decode forwards.
346#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
347pub struct DensePassReport {
348    forwards: u64,
349    host_cache: DenseCacheMetrics,
350    device_cache: DenseCacheMetrics,
351    peak_host_layers: usize,
352    peak_host_bytes: u64,
353    peak_device_layers: usize,
354    peak_device_bytes: u64,
355    disk_to_host_bytes: u64,
356    disk_to_device_bytes: u64,
357    host_to_device_bytes: u64,
358}
359
360impl DensePassReport {
361    /// Returns completed forwards in this pass category.
362    pub const fn forwards(self) -> u64 {
363        self.forwards
364    }
365    /// Returns host-cache activity during completed forwards.
366    pub const fn host_cache(self) -> DenseCacheMetrics {
367        self.host_cache
368    }
369    /// Returns device-cache activity during completed forwards.
370    pub const fn device_cache(self) -> DenseCacheMetrics {
371        self.device_cache
372    }
373    /// Returns peak host-resident streamed layers observed during these forwards.
374    pub const fn peak_host_layers(self) -> usize {
375        self.peak_host_layers
376    }
377    /// Returns peak host-resident streamed-layer bytes during these forwards.
378    pub const fn peak_host_bytes(self) -> u64 {
379        self.peak_host_bytes
380    }
381    /// Returns peak device-resident streamed layers observed during these forwards.
382    pub const fn peak_device_layers(self) -> usize {
383        self.peak_device_layers
384    }
385    /// Returns peak device-resident streamed-layer bytes during these forwards.
386    pub const fn peak_device_bytes(self) -> u64 {
387        self.peak_device_bytes
388    }
389    /// Returns logical disk-to-host bytes during completed forwards.
390    pub const fn disk_to_host_bytes(self) -> u64 {
391        self.disk_to_host_bytes
392    }
393    /// Returns logical disk-to-device bytes during completed forwards.
394    pub const fn disk_to_device_bytes(self) -> u64 {
395        self.disk_to_device_bytes
396    }
397    /// Returns logical host-to-device bytes during completed forwards.
398    pub const fn host_to_device_bytes(self) -> u64 {
399        self.host_to_device_bytes
400    }
401
402    /// Replaces point-in-time peaks on one completed-pass delta.
403    pub fn set_peaks(
404        &mut self,
405        host_layers: usize,
406        host_bytes: u64,
407        device_layers: usize,
408        device_bytes: u64,
409    ) {
410        self.peak_host_layers = host_layers;
411        self.peak_host_bytes = host_bytes;
412        self.peak_device_layers = device_layers;
413        self.peak_device_bytes = device_bytes;
414    }
415
416    /// Accumulates a completed pass with saturating counters and maximum peaks.
417    pub fn accumulate(&mut self, other: Self) {
418        self.forwards = self.forwards.saturating_add(other.forwards);
419        self.host_cache.saturating_add(other.host_cache);
420        self.device_cache.saturating_add(other.device_cache);
421        self.peak_host_layers = self.peak_host_layers.max(other.peak_host_layers);
422        self.peak_host_bytes = self.peak_host_bytes.max(other.peak_host_bytes);
423        self.peak_device_layers = self.peak_device_layers.max(other.peak_device_layers);
424        self.peak_device_bytes = self.peak_device_bytes.max(other.peak_device_bytes);
425        self.disk_to_host_bytes = self
426            .disk_to_host_bytes
427            .saturating_add(other.disk_to_host_bytes);
428        self.disk_to_device_bytes = self
429            .disk_to_device_bytes
430            .saturating_add(other.disk_to_device_bytes);
431        self.host_to_device_bytes = self
432            .host_to_device_bytes
433            .saturating_add(other.host_to_device_bytes);
434    }
435}
436
437/// Counter snapshot used to attribute residency activity to one forward pass.
438#[derive(Debug, Default, Clone, Copy)]
439pub struct DensePassCounterSnapshot {
440    host_cache: DenseCacheMetrics,
441    device_cache: DenseCacheMetrics,
442    disk_to_host_bytes: u64,
443    disk_to_device_bytes: u64,
444    host_to_device_bytes: u64,
445}
446
447impl DensePassCounterSnapshot {
448    /// Captures logical cache and transfer counters from an offload report.
449    pub fn from_report(report: &OffloadReport) -> Self {
450        Self {
451            host_cache: DenseCacheMetrics::from_report(report, MemoryTier::Host),
452            device_cache: DenseCacheMetrics::from_report(report, MemoryTier::Device),
453            disk_to_host_bytes: report.transfer(TransferDirection::DiskToHost).bytes(),
454            disk_to_device_bytes: report.transfer(TransferDirection::DiskToDevice).bytes(),
455            host_to_device_bytes: report.transfer(TransferDirection::HostToDevice).bytes(),
456        }
457    }
458
459    /// Computes the saturating completed-forward delta from an earlier snapshot.
460    pub fn delta(self, earlier: Self) -> DensePassReport {
461        DensePassReport {
462            forwards: 1,
463            host_cache: self.host_cache.saturating_delta(earlier.host_cache),
464            device_cache: self.device_cache.saturating_delta(earlier.device_cache),
465            peak_host_layers: 0,
466            peak_host_bytes: 0,
467            peak_device_layers: 0,
468            peak_device_bytes: 0,
469            disk_to_host_bytes: self
470                .disk_to_host_bytes
471                .saturating_sub(earlier.disk_to_host_bytes),
472            disk_to_device_bytes: self
473                .disk_to_device_bytes
474                .saturating_sub(earlier.disk_to_device_bytes),
475            host_to_device_bytes: self
476                .host_to_device_bytes
477                .saturating_sub(earlier.host_to_device_bytes),
478        }
479    }
480}
481
482#[derive(Debug)]
483struct DensePassState {
484    active: Option<DensePassActivity>,
485    prefill: DensePassReport,
486    decode: DensePassReport,
487}
488
489#[derive(Debug, Clone, Copy)]
490struct DensePassActivity {
491    prefill: bool,
492    start: DensePassCounterSnapshot,
493    peaks: DensePassReport,
494}
495
496#[derive(Debug, Clone)]
497struct DenseExecutionGroupPlan {
498    id: String,
499    units: Vec<OffloadUnitId>,
500}
501
502#[derive(Debug, Default, Clone, Copy)]
503struct DenseExecutionGroupState {
504    completed_executions: u64,
505    peak_host_layers: usize,
506    peak_host_bytes: u64,
507    peak_device_layers: usize,
508    peak_device_bytes: u64,
509}
510
511/// Backend-neutral lifecycle and aggregation state for dense-stream telemetry.
512#[derive(Debug)]
513pub struct DenseStreamTelemetry {
514    planned_layer_count: usize,
515    planned_layer_bytes: u64,
516    maximum_host_layer_bytes: u64,
517    pinned_static_device_bytes: u64,
518    transfer_stream_index: i32,
519    groups: Vec<DenseExecutionGroupPlan>,
520    group_activity: Mutex<BTreeMap<String, DenseExecutionGroupState>>,
521    pass: Mutex<DensePassState>,
522}
523
524impl DenseStreamTelemetry {
525    /// Creates telemetry state for a fixed execution-group and residency plan.
526    pub fn new(
527        planned_layer_count: usize,
528        planned_layer_bytes: u64,
529        maximum_host_layer_bytes: u64,
530        pinned_static_device_bytes: u64,
531        transfer_stream_index: i32,
532        groups: impl IntoIterator<Item = (String, Vec<OffloadUnitId>)>,
533    ) -> Self {
534        let groups = groups
535            .into_iter()
536            .map(|(id, units)| DenseExecutionGroupPlan { id, units })
537            .collect::<Vec<_>>();
538        let group_activity = groups
539            .iter()
540            .map(|group| (group.id.clone(), DenseExecutionGroupState::default()))
541            .collect();
542        Self {
543            planned_layer_count,
544            planned_layer_bytes,
545            maximum_host_layer_bytes,
546            pinned_static_device_bytes,
547            transfer_stream_index,
548            groups,
549            group_activity: Mutex::new(group_activity),
550            pass: Mutex::new(DensePassState {
551                active: None,
552                prefill: DensePassReport::default(),
553                decode: DensePassReport::default(),
554            }),
555        }
556    }
557
558    /// Starts attribution for one prefill or decode pass.
559    pub fn begin_forward(
560        &self,
561        prefill: bool,
562        offload: &OffloadReport,
563    ) -> Result<(), DenseStreamTelemetryError> {
564        let mut state = self
565            .pass
566            .lock()
567            .map_err(|_| DenseStreamTelemetryError::StatePoisoned)?;
568        if state.active.is_some() {
569            return Err(DenseStreamTelemetryError::InvalidForwardState(
570                "a forward is already active",
571            ));
572        }
573        state.active = Some(DensePassActivity {
574            prefill,
575            start: DensePassCounterSnapshot::from_report(offload),
576            peaks: DensePassReport::default(),
577        });
578        Ok(())
579    }
580
581    /// Records current group and whole-plan occupancy during an active pass.
582    pub fn observe_group(
583        &self,
584        group: &str,
585        prefill: bool,
586        units: &[UnitResidencyReport],
587    ) -> Result<(), DenseStreamTelemetryError> {
588        let plan = self
589            .groups
590            .iter()
591            .find(|candidate| candidate.id == group)
592            .ok_or_else(|| DenseStreamTelemetryError::UnknownExecutionGroup(group.to_string()))?;
593        let ids = plan.units.iter().collect::<BTreeSet<_>>();
594        let group_units = units
595            .iter()
596            .filter(|unit| ids.contains(unit.id()))
597            .collect::<Vec<_>>();
598        let (host_layers, host_bytes, device_layers, device_bytes) = occupancy(&group_units);
599        let mut activity = self
600            .group_activity
601            .lock()
602            .map_err(|_| DenseStreamTelemetryError::StatePoisoned)?;
603        let state = activity
604            .get_mut(group)
605            .ok_or_else(|| DenseStreamTelemetryError::UnknownExecutionGroup(group.to_string()))?;
606        state.peak_host_layers = state.peak_host_layers.max(host_layers);
607        state.peak_host_bytes = state.peak_host_bytes.max(host_bytes);
608        state.peak_device_layers = state.peak_device_layers.max(device_layers);
609        state.peak_device_bytes = state.peak_device_bytes.max(device_bytes);
610        drop(activity);
611
612        let streamed = self
613            .groups
614            .iter()
615            .flat_map(|group| group.units.iter())
616            .collect::<BTreeSet<_>>();
617        let streamed_units = units
618            .iter()
619            .filter(|unit| streamed.contains(unit.id()))
620            .collect::<Vec<_>>();
621        let (host_layers, host_bytes, device_layers, device_bytes) = occupancy(&streamed_units);
622        let mut pass = self
623            .pass
624            .lock()
625            .map_err(|_| DenseStreamTelemetryError::StatePoisoned)?;
626        let active = pass
627            .active
628            .as_mut()
629            .ok_or(DenseStreamTelemetryError::InvalidForwardState(
630                "residency was observed without an active forward",
631            ))?;
632        if active.prefill != prefill {
633            return Err(DenseStreamTelemetryError::InvalidForwardState(
634                "residency observation changed pass category",
635            ));
636        }
637        active.peaks.set_peaks(
638            active.peaks.peak_host_layers().max(host_layers),
639            active.peaks.peak_host_bytes().max(host_bytes),
640            active.peaks.peak_device_layers().max(device_layers),
641            active.peaks.peak_device_bytes().max(device_bytes),
642        );
643        Ok(())
644    }
645
646    /// Records successful completion of one named execution group.
647    pub fn record_group_execution(&self, group: &str) -> Result<(), DenseStreamTelemetryError> {
648        let mut activity = self
649            .group_activity
650            .lock()
651            .map_err(|_| DenseStreamTelemetryError::StatePoisoned)?;
652        let state = activity
653            .get_mut(group)
654            .ok_or_else(|| DenseStreamTelemetryError::UnknownExecutionGroup(group.to_string()))?;
655        state.completed_executions = state.completed_executions.saturating_add(1);
656        Ok(())
657    }
658
659    /// Commits counter deltas and occupancy peaks for the active pass.
660    pub fn commit_forward(&self, offload: &OffloadReport) -> Result<(), DenseStreamTelemetryError> {
661        let current = DensePassCounterSnapshot::from_report(offload);
662        let mut state = self
663            .pass
664            .lock()
665            .map_err(|_| DenseStreamTelemetryError::StatePoisoned)?;
666        let active = state
667            .active
668            .take()
669            .ok_or(DenseStreamTelemetryError::InvalidForwardState(
670                "a forward was committed without being started",
671            ))?;
672        let mut delta = current.delta(active.start);
673        delta.set_peaks(
674            active.peaks.peak_host_layers(),
675            active.peaks.peak_host_bytes(),
676            active.peaks.peak_device_layers(),
677            active.peaks.peak_device_bytes(),
678        );
679        if active.prefill {
680            state.prefill.accumulate(delta);
681        } else {
682            state.decode.accumulate(delta);
683        }
684        Ok(())
685    }
686
687    /// Aborts the active pass without committing partial counter deltas.
688    pub fn abort_forward(&self) {
689        if let Ok(mut state) = self.pass.lock() {
690            state.active = None;
691        }
692    }
693
694    /// Builds a stable report from a coherent residency and worker snapshot.
695    pub fn report(
696        &self,
697        residency: ResidencyReport,
698        background: BackgroundPrefetchReport,
699    ) -> Result<DenseDiskStreamReport, DenseStreamTelemetryError> {
700        let streamed = self
701            .groups
702            .iter()
703            .flat_map(|group| group.units.iter())
704            .collect::<BTreeSet<_>>();
705        let units = residency
706            .units()
707            .iter()
708            .map(|unit| (unit.id(), unit))
709            .collect::<BTreeMap<_, _>>();
710        let pinned_device_bytes = residency
711            .units()
712            .iter()
713            .filter(|unit| unit.policy() == ResidencyPolicy::Pinned && unit.device_resident())
714            .map(UnitResidencyReport::device_allocated_bytes)
715            .sum::<u64>();
716        let pinned_device_count = residency
717            .units()
718            .iter()
719            .filter(|unit| unit.policy() == ResidencyPolicy::Pinned && unit.device_resident())
720            .count();
721        let tier_report = |tier: MemoryTier| {
722            let current = residency
723                .units()
724                .iter()
725                .filter(|unit| streamed.contains(unit.id()))
726                .filter(|unit| match tier {
727                    MemoryTier::Host => unit.host_resident(),
728                    MemoryTier::Device => unit.device_resident(),
729                    MemoryTier::Disk => false,
730                })
731                .collect::<Vec<_>>();
732            let (pinned_bytes, pinned_count) = if tier == MemoryTier::Device {
733                (pinned_device_bytes, pinned_device_count)
734            } else {
735                (0, 0)
736            };
737            DenseTierResidencyReport::new(
738                current.len(),
739                residency
740                    .offload()
741                    .peak_resident_units()
742                    .get(tier)
743                    .saturating_sub(pinned_count),
744                current
745                    .iter()
746                    .map(|unit| match tier {
747                        MemoryTier::Host => unit.host_allocated_bytes(),
748                        MemoryTier::Device => unit.device_allocated_bytes(),
749                        MemoryTier::Disk => 0,
750                    })
751                    .sum(),
752                residency
753                    .offload()
754                    .peak_resident_bytes()
755                    .get(tier)
756                    .saturating_sub(pinned_bytes),
757                DenseCacheMetrics::from_report(residency.offload(), tier),
758            )
759        };
760        let activity = self
761            .group_activity
762            .lock()
763            .map_err(|_| DenseStreamTelemetryError::StatePoisoned)?;
764        let groups = self
765            .groups
766            .iter()
767            .map(|group| {
768                let group_units = group
769                    .units
770                    .iter()
771                    .filter_map(|id| units.get(id).copied())
772                    .collect::<Vec<_>>();
773                let observed = activity.get(&group.id).copied().unwrap_or_default();
774                let (host_layers, host_bytes, device_layers, device_bytes) =
775                    occupancy(&group_units);
776                DenseExecutionGroupReport::new(
777                    group.id.clone(),
778                    group_units.len(),
779                    group_units.iter().map(|unit| unit.expected_bytes()).sum(),
780                    observed.completed_executions,
781                    host_layers,
782                    host_bytes,
783                    observed.peak_host_layers,
784                    observed.peak_host_bytes,
785                    device_layers,
786                    device_bytes,
787                    observed.peak_device_layers,
788                    observed.peak_device_bytes,
789                )
790            })
791            .collect();
792        let pass = self
793            .pass
794            .lock()
795            .map_err(|_| DenseStreamTelemetryError::StatePoisoned)?;
796        let host_layers = tier_report(MemoryTier::Host);
797        let device_layers = tier_report(MemoryTier::Device);
798        Ok(DenseDiskStreamReport::new(
799            self.planned_layer_count,
800            self.planned_layer_bytes,
801            self.maximum_host_layer_bytes,
802            self.pinned_static_device_bytes,
803            self.transfer_stream_index,
804            residency,
805            background,
806            host_layers,
807            device_layers,
808            groups,
809            pass.prefill,
810            pass.decode,
811        ))
812    }
813}
814
815fn occupancy(units: &[&UnitResidencyReport]) -> (usize, u64, usize, u64) {
816    let host_layers = units.iter().filter(|unit| unit.host_resident()).count();
817    let host_bytes = units
818        .iter()
819        .filter(|unit| unit.host_resident())
820        .map(|unit| unit.host_allocated_bytes())
821        .sum();
822    let device_layers = units.iter().filter(|unit| unit.device_resident()).count();
823    let device_bytes = units
824        .iter()
825        .filter(|unit| unit.device_resident())
826        .map(|unit| unit.device_allocated_bytes())
827        .sum();
828    (host_layers, host_bytes, device_layers, device_bytes)
829}
830
831/// Invalid dense-stream telemetry lifecycle or execution-group access.
832#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
833pub enum DenseStreamTelemetryError {
834    /// Shared telemetry state was poisoned.
835    #[error("dense streaming telemetry state is poisoned")]
836    StatePoisoned,
837    /// Forward lifecycle calls were inconsistent.
838    #[error("invalid dense streaming forward telemetry state: {0}")]
839    InvalidForwardState(&'static str),
840    /// The requested execution group was not declared.
841    #[error("unknown dense streaming execution group {0}")]
842    UnknownExecutionGroup(String),
843}
844
845#[cfg(test)]
846mod tests {
847    use super::*;
848    use eredu_core::residency::OffloadTelemetry;
849
850    #[test]
851    fn pass_accumulation_preserves_maximum_peaks() {
852        let mut total = DensePassReport::default();
853        let mut first = DensePassReport::default();
854        first.set_peaks(2, 20, 3, 30);
855        let mut second = DensePassReport::default();
856        second.set_peaks(4, 10, 1, 40);
857        total.accumulate(first);
858        total.accumulate(second);
859        assert_eq!(total.peak_host_layers(), 4);
860        assert_eq!(total.peak_host_bytes(), 20);
861        assert_eq!(total.peak_device_layers(), 3);
862        assert_eq!(total.peak_device_bytes(), 40);
863    }
864
865    #[test]
866    fn telemetry_owns_forward_lifecycle_validation() {
867        let telemetry = DenseStreamTelemetry::new(2, 20, 10, 5, 3, []);
868        let offload = OffloadTelemetry::default().snapshot();
869        telemetry.begin_forward(true, &offload).unwrap();
870        assert_eq!(
871            telemetry.begin_forward(true, &offload),
872            Err(DenseStreamTelemetryError::InvalidForwardState(
873                "a forward is already active"
874            ))
875        );
876        telemetry.abort_forward();
877        telemetry.begin_forward(false, &offload).unwrap();
878        telemetry.commit_forward(&offload).unwrap();
879        assert_eq!(
880            telemetry.commit_forward(&offload),
881            Err(DenseStreamTelemetryError::InvalidForwardState(
882                "a forward was committed without being started"
883            ))
884        );
885    }
886}