1use 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#[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 #[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 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 pub const fn planned_layer_count(&self) -> usize {
75 self.planned_layer_count
76 }
77 pub const fn planned_layer_bytes(&self) -> u64 {
79 self.planned_layer_bytes
80 }
81 pub const fn maximum_host_layer_bytes(&self) -> u64 {
83 self.maximum_host_layer_bytes
84 }
85 pub const fn pinned_static_device_bytes(&self) -> u64 {
87 self.pinned_static_device_bytes
88 }
89 pub const fn transfer_stream_index(&self) -> i32 {
91 self.transfer_stream_index
92 }
93 pub const fn residency(&self) -> &ResidencyReport {
95 &self.residency
96 }
97 pub const fn background(&self) -> BackgroundPrefetchReport {
99 self.background
100 }
101 pub const fn host_layers(&self) -> DenseTierResidencyReport {
103 self.host_layers
104 }
105 pub const fn device_layers(&self) -> DenseTierResidencyReport {
107 self.device_layers
108 }
109 pub fn execution_groups(&self) -> &[DenseExecutionGroupReport] {
111 &self.groups
112 }
113 pub const fn prefill(&self) -> DensePassReport {
115 self.prefill
116 }
117 pub const fn decode(&self) -> DensePassReport {
119 self.decode
120 }
121 pub const fn prefill_forwards(&self) -> u64 {
123 self.prefill.forwards
124 }
125 pub const fn decode_forwards(&self) -> u64 {
127 self.decode.forwards
128 }
129}
130
131#[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 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 pub const fn requests(self) -> u64 {
157 self.requests
158 }
159 pub const fn hits(self) -> u64 {
161 self.hits
162 }
163 pub const fn misses(self) -> u64 {
165 self.misses
166 }
167 pub const fn evictions(self) -> u64 {
169 self.evictions
170 }
171 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#[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 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 pub const fn current_layer_count(self) -> usize {
225 self.current_layer_count
226 }
227 pub const fn peak_layer_count(self) -> usize {
229 self.peak_layer_count
230 }
231 pub const fn current_layer_bytes(self) -> u64 {
233 self.current_layer_bytes
234 }
235 pub const fn peak_layer_bytes(self) -> u64 {
237 self.peak_layer_bytes
238 }
239 pub const fn cache(self) -> DenseCacheMetrics {
241 self.cache
242 }
243}
244
245#[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 #[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 pub fn id(&self) -> &str {
297 &self.id
298 }
299 pub const fn planned_layers(&self) -> usize {
301 self.planned_layers
302 }
303 pub const fn planned_bytes(&self) -> u64 {
305 self.planned_bytes
306 }
307 pub const fn completed_executions(&self) -> u64 {
309 self.completed_executions
310 }
311 pub const fn host_layers(&self) -> usize {
313 self.host_layers
314 }
315 pub const fn host_bytes(&self) -> u64 {
317 self.host_bytes
318 }
319 pub const fn peak_host_layers(&self) -> usize {
321 self.peak_host_layers
322 }
323 pub const fn peak_host_bytes(&self) -> u64 {
325 self.peak_host_bytes
326 }
327 pub const fn device_layers(&self) -> usize {
329 self.device_layers
330 }
331 pub const fn device_bytes(&self) -> u64 {
333 self.device_bytes
334 }
335 pub const fn peak_device_layers(&self) -> usize {
337 self.peak_device_layers
338 }
339 pub const fn peak_device_bytes(&self) -> u64 {
341 self.peak_device_bytes
342 }
343}
344
345#[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 pub const fn forwards(self) -> u64 {
363 self.forwards
364 }
365 pub const fn host_cache(self) -> DenseCacheMetrics {
367 self.host_cache
368 }
369 pub const fn device_cache(self) -> DenseCacheMetrics {
371 self.device_cache
372 }
373 pub const fn peak_host_layers(self) -> usize {
375 self.peak_host_layers
376 }
377 pub const fn peak_host_bytes(self) -> u64 {
379 self.peak_host_bytes
380 }
381 pub const fn peak_device_layers(self) -> usize {
383 self.peak_device_layers
384 }
385 pub const fn peak_device_bytes(self) -> u64 {
387 self.peak_device_bytes
388 }
389 pub const fn disk_to_host_bytes(self) -> u64 {
391 self.disk_to_host_bytes
392 }
393 pub const fn disk_to_device_bytes(self) -> u64 {
395 self.disk_to_device_bytes
396 }
397 pub const fn host_to_device_bytes(self) -> u64 {
399 self.host_to_device_bytes
400 }
401
402 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 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#[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 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 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#[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 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 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 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 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 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 pub fn abort_forward(&self) {
689 if let Ok(mut state) = self.pass.lock() {
690 state.active = None;
691 }
692 }
693
694 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#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
833pub enum DenseStreamTelemetryError {
834 #[error("dense streaming telemetry state is poisoned")]
836 StatePoisoned,
837 #[error("invalid dense streaming forward telemetry state: {0}")]
839 InvalidForwardState(&'static str),
840 #[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}