1use std::collections::VecDeque;
4
5use eredu_core::{
6 residency::{CacheEvictionPolicy, OffloadConfig, OffloadError, OffloadUnitId},
7 DEFAULT_MAX_CACHED_SHARDS,
8};
9
10use crate::WeightBinding;
11
12pub const DENSE_TRANSFER_WINDOW: usize = 2;
14
15#[derive(Debug, Clone, Eq, PartialEq)]
17pub struct StaticUnitBindings {
18 id: OffloadUnitId,
19 bindings: Vec<WeightBinding>,
20}
21
22impl StaticUnitBindings {
23 pub fn new(id: impl Into<String>, bindings: Vec<WeightBinding>) -> Result<Self, OffloadError> {
25 Ok(Self {
26 id: OffloadUnitId::new(id.into())?,
27 bindings,
28 })
29 }
30
31 pub const fn id(&self) -> &OffloadUnitId {
33 &self.id
34 }
35
36 pub fn bindings(&self) -> &[WeightBinding] {
38 &self.bindings
39 }
40
41 pub fn into_parts(self) -> (OffloadUnitId, Vec<WeightBinding>) {
43 (self.id, self.bindings)
44 }
45}
46
47#[derive(Debug)]
49pub struct DenseTransferSchedule<T> {
50 capacity: usize,
51 pending: VecDeque<usize>,
52 ready: VecDeque<(usize, T)>,
53}
54
55impl<T> DenseTransferSchedule<T> {
56 pub fn new(
58 pending: impl IntoIterator<Item = usize>,
59 capacity: usize,
60 ) -> Result<Self, DenseTransferScheduleError> {
61 if capacity == 0 {
62 return Err(DenseTransferScheduleError::ZeroCapacity);
63 }
64 let pending = pending.into_iter().collect::<VecDeque<_>>();
65 let mut previous = None;
66 for &index in &pending {
67 if previous.is_some_and(|previous| previous >= index) {
68 return Err(DenseTransferScheduleError::UnorderedPending {
69 previous: previous.expect("an invalid pair has a previous index"),
70 actual: index,
71 });
72 }
73 previous = Some(index);
74 }
75 Ok(Self {
76 capacity,
77 pending,
78 ready: VecDeque::new(),
79 })
80 }
81
82 pub fn has_ready(&self) -> bool {
84 !self.ready.is_empty()
85 }
86
87 pub fn is_exhausted(&self) -> bool {
89 self.ready.is_empty() && self.pending.is_empty()
90 }
91
92 pub fn can_admit(&self) -> bool {
94 self.ready.len() < self.capacity && !self.pending.is_empty()
95 }
96
97 pub fn next_pending(&self) -> Option<usize> {
99 self.pending.front().copied()
100 }
101
102 pub fn desired_indices(&self, lookahead: usize) -> Vec<usize> {
104 self.ready
105 .iter()
106 .map(|(index, _)| *index)
107 .chain(self.pending.iter().copied())
108 .take(lookahead)
109 .collect()
110 }
111
112 pub fn admit(&mut self, index: usize, transfer: T) -> Result<(), DenseTransferScheduleError> {
114 if self.ready.len() >= self.capacity {
115 return Err(DenseTransferScheduleError::CapacityExceeded {
116 capacity: self.capacity,
117 });
118 }
119 let expected = self
120 .pending
121 .front()
122 .copied()
123 .ok_or(DenseTransferScheduleError::NoPendingUnit)?;
124 if expected != index {
125 return Err(DenseTransferScheduleError::OutOfOrder {
126 expected,
127 actual: index,
128 });
129 }
130 self.pending.pop_front();
131 self.ready.push_back((index, transfer));
132 Ok(())
133 }
134
135 pub fn pop_ready(&mut self) -> Option<(usize, T)> {
137 self.ready.pop_front()
138 }
139}
140
141#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
143#[non_exhaustive]
144pub enum DenseTransferScheduleError {
145 #[error("dense transfer window capacity must be nonzero")]
147 ZeroCapacity,
148 #[error("dense transfer pending unit {actual} does not follow {previous}")]
150 UnorderedPending {
151 previous: usize,
153 actual: usize,
155 },
156 #[error("dense transfer window exceeds its capacity of {capacity}")]
158 CapacityExceeded {
159 capacity: usize,
161 },
162 #[error("dense transfer schedule has no pending unit")]
164 NoPendingUnit,
165 #[error("dense transfer schedule expected unit {expected}, received {actual}")]
167 OutOfOrder {
168 expected: usize,
170 actual: usize,
172 },
173}
174
175#[derive(Debug, Clone, Copy, Eq, PartialEq)]
177pub struct LayerwiseLoadOptions {
178 offload: OffloadConfig,
180 max_cached_shards: usize,
182 sample_backend_memory: bool,
184 sample_process_memory: bool,
186}
187
188impl LayerwiseLoadOptions {
189 pub fn new(offload: OffloadConfig) -> Self {
191 Self {
192 offload,
193 ..Self::default()
194 }
195 }
196
197 pub const fn with_max_cached_shards(mut self, maximum: usize) -> Self {
199 self.max_cached_shards = maximum;
200 self
201 }
202 pub const fn with_memory_sampling(mut self, backend: bool, process: bool) -> Self {
204 self.sample_backend_memory = backend;
205 self.sample_process_memory = process;
206 self
207 }
208 pub const fn offload(self) -> OffloadConfig {
210 self.offload
211 }
212 pub const fn max_cached_shards(self) -> usize {
214 self.max_cached_shards
215 }
216 pub const fn samples_backend_memory(self) -> bool {
218 self.sample_backend_memory
219 }
220 pub const fn samples_process_memory(self) -> bool {
222 self.sample_process_memory
223 }
224}
225
226impl Default for LayerwiseLoadOptions {
227 fn default() -> Self {
228 Self {
229 offload: OffloadConfig::default(),
230 max_cached_shards: DEFAULT_MAX_CACHED_SHARDS,
231 sample_backend_memory: false,
232 sample_process_memory: false,
233 }
234 }
235}
236
237#[derive(Debug, Clone, Copy, Eq, PartialEq)]
239pub struct DenseDiskStreamLoadOptions {
240 device_budget_bytes: u64,
242 host_budget_bytes: u64,
244 host_lookahead: usize,
246 background_queue_capacity: usize,
248 eviction_policy: CacheEvictionPolicy,
250 max_cached_shards: usize,
252 sample_backend_memory: bool,
254 sample_process_memory: bool,
256}
257
258impl DenseDiskStreamLoadOptions {
259 pub fn new(
261 device_budget_bytes: u64,
262 host_budget_bytes: u64,
263 host_lookahead: usize,
264 background_queue_capacity: usize,
265 ) -> Result<Self, WeightResidencyPolicyError> {
266 let options = Self {
267 device_budget_bytes,
268 host_budget_bytes,
269 host_lookahead,
270 background_queue_capacity,
271 eviction_policy: CacheEvictionPolicy::LeastRecentlyUsed,
272 max_cached_shards: DEFAULT_MAX_CACHED_SHARDS,
273 sample_backend_memory: false,
274 sample_process_memory: false,
275 };
276 options.validate()?;
277 Ok(options)
278 }
279
280 pub fn validate(self) -> Result<(), WeightResidencyPolicyError> {
282 if self.host_budget_bytes == 0 {
283 if self.host_lookahead != 0 || self.background_queue_capacity != 0 {
284 return Err(WeightResidencyPolicyError::HostDisabledControls);
285 }
286 } else {
287 if self.host_lookahead == 0 {
288 return Err(WeightResidencyPolicyError::ZeroHostLookahead);
289 }
290 if self.background_queue_capacity == 0 {
291 return Err(WeightResidencyPolicyError::ZeroQueueCapacity);
292 }
293 }
294 Ok(())
295 }
296
297 pub const fn with_eviction_policy(mut self, policy: CacheEvictionPolicy) -> Self {
299 self.eviction_policy = policy;
300 self
301 }
302
303 pub const fn with_max_cached_shards(mut self, maximum: usize) -> Self {
305 self.max_cached_shards = maximum;
306 self
307 }
308 pub const fn with_memory_sampling(mut self, backend: bool, process: bool) -> Self {
310 self.sample_backend_memory = backend;
311 self.sample_process_memory = process;
312 self
313 }
314 pub const fn device_budget_bytes(self) -> u64 {
316 self.device_budget_bytes
317 }
318 pub const fn host_budget_bytes(self) -> u64 {
320 self.host_budget_bytes
321 }
322 pub const fn host_lookahead(self) -> usize {
324 self.host_lookahead
325 }
326 pub const fn background_queue_capacity(self) -> usize {
328 self.background_queue_capacity
329 }
330 pub const fn eviction_policy(self) -> CacheEvictionPolicy {
332 self.eviction_policy
333 }
334 pub const fn max_cached_shards(self) -> usize {
336 self.max_cached_shards
337 }
338 pub const fn samples_backend_memory(self) -> bool {
340 self.sample_backend_memory
341 }
342 pub const fn samples_process_memory(self) -> bool {
344 self.sample_process_memory
345 }
346}
347
348impl Default for DenseDiskStreamLoadOptions {
349 fn default() -> Self {
350 Self::new(4 << 30, 16 << 30, 2, 2).expect("default dense disk streaming controls are valid")
351 }
352}
353
354#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
356#[non_exhaustive]
357pub enum LayerWeightResidency {
358 #[default]
360 FullyResident,
361 LayerwiseHost(LayerwiseLoadOptions),
363 DenseDiskStream(DenseDiskStreamLoadOptions),
365}
366
367#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
369pub struct ParameterBankKey {
370 unit: usize,
371 member: usize,
372}
373
374impl ParameterBankKey {
375 pub const fn new(unit: usize, member: usize) -> Self {
377 Self { unit, member }
378 }
379
380 pub const fn unit(self) -> usize {
382 self.unit
383 }
384
385 pub const fn member(self) -> usize {
387 self.member
388 }
389
390 pub fn unit_id(self) -> OffloadUnitId {
392 OffloadUnitId::new(format!(
393 "bank.unit.{:05}.member.{:05}",
394 self.unit, self.member
395 ))
396 .expect("parameter-bank unit identifier is non-empty")
397 }
398}
399
400#[derive(Debug, Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
402#[non_exhaustive]
403pub enum ExpertPass {
404 Prefill,
406 Decode,
408}
409
410#[derive(Debug, Clone, Copy, Eq, PartialEq)]
412pub struct ParameterBankLoadOptions {
413 members: OffloadConfig,
415 compact_bank_scratch_bytes: u64,
417 prefill_compact_bank_target_bytes: u64,
419}
420
421impl ParameterBankLoadOptions {
422 pub fn new(
424 members: OffloadConfig,
425 compact_bank_scratch_bytes: u64,
426 prefill_compact_bank_target_bytes: u64,
427 ) -> Result<Self, WeightResidencyPolicyError> {
428 let options = Self {
429 members,
430 compact_bank_scratch_bytes,
431 prefill_compact_bank_target_bytes,
432 };
433 options.validate()?;
434 Ok(options)
435 }
436
437 pub fn validate(self) -> Result<(), WeightResidencyPolicyError> {
439 if self.compact_bank_scratch_bytes == 0 {
440 return Err(WeightResidencyPolicyError::ZeroParameterBankScratchLimit);
441 }
442 if self.prefill_compact_bank_target_bytes == 0 {
443 return Err(WeightResidencyPolicyError::ZeroParameterBankPrefillTarget);
444 }
445 if self.prefill_compact_bank_target_bytes > self.compact_bank_scratch_bytes {
446 return Err(
447 WeightResidencyPolicyError::ParameterBankPrefillTargetExceedsScratch {
448 target_bytes: self.prefill_compact_bank_target_bytes,
449 scratch_bytes: self.compact_bank_scratch_bytes,
450 },
451 );
452 }
453 Ok(())
454 }
455
456 pub const fn offload(self) -> OffloadConfig {
458 self.members
459 }
460 pub const fn compact_bank_scratch_bytes(self) -> u64 {
462 self.compact_bank_scratch_bytes
463 }
464 pub const fn prefill_compact_bank_target_bytes(self) -> u64 {
466 self.prefill_compact_bank_target_bytes
467 }
468}
469
470impl Default for ParameterBankLoadOptions {
471 fn default() -> Self {
472 Self {
473 members: OffloadConfig::default(),
474 compact_bank_scratch_bytes: u64::MAX,
475 prefill_compact_bank_target_bytes: 1 << 30,
476 }
477 }
478}
479
480#[derive(Debug, Clone, Copy, Eq, PartialEq)]
482#[non_exhaustive]
483pub enum OrdinaryWeightResidency {
484 FullyResident,
486 LayerwiseHost(LayerwiseLoadOptions),
488 DenseDiskStream(DenseDiskStreamLoadOptions),
490}
491
492impl OrdinaryWeightResidency {
493 pub const fn layers(self) -> LayerWeightResidency {
495 match self {
496 Self::FullyResident => LayerWeightResidency::FullyResident,
497 Self::LayerwiseHost(options) => LayerWeightResidency::LayerwiseHost(options),
498 Self::DenseDiskStream(options) => LayerWeightResidency::DenseDiskStream(options),
499 }
500 }
501}
502
503impl From<OrdinaryWeightResidency> for LayerWeightResidency {
504 fn from(value: OrdinaryWeightResidency) -> Self {
505 value.layers()
506 }
507}
508
509#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
511#[non_exhaustive]
512pub enum ParameterBankResidency {
513 #[default]
515 WithLayer,
516 IndependentCache(ParameterBankLoadOptions),
518}
519
520#[derive(Debug, Clone, Copy, Eq, PartialEq)]
522#[non_exhaustive]
523pub enum WeightResidency {
524 Layers(LayerWeightResidency),
526 #[non_exhaustive]
528 IndependentParameterBanks {
529 ordinary: OrdinaryWeightResidency,
531 cache: ParameterBankLoadOptions,
533 },
534}
535
536impl WeightResidency {
537 pub const fn fully_resident() -> Self {
539 Self::with_layers(LayerWeightResidency::FullyResident)
540 }
541
542 pub const fn layerwise_host(options: LayerwiseLoadOptions) -> Self {
544 Self::with_layers(LayerWeightResidency::LayerwiseHost(options))
545 }
546
547 pub const fn dense_disk_stream(options: DenseDiskStreamLoadOptions) -> Self {
549 Self::with_layers(LayerWeightResidency::DenseDiskStream(options))
550 }
551
552 pub const fn with_layers(layers: LayerWeightResidency) -> Self {
554 Self::Layers(layers)
555 }
556
557 pub const fn with_independent_parameter_banks(
559 ordinary: OrdinaryWeightResidency,
560 cache: ParameterBankLoadOptions,
561 ) -> Self {
562 Self::IndependentParameterBanks { ordinary, cache }
563 }
564
565 pub const fn layers(self) -> LayerWeightResidency {
567 match self {
568 Self::Layers(layers) => layers,
569 Self::IndependentParameterBanks { ordinary, .. } => ordinary.layers(),
570 }
571 }
572
573 pub const fn parameter_banks(self) -> ParameterBankResidency {
575 match self {
576 Self::Layers(_) => ParameterBankResidency::WithLayer,
577 Self::IndependentParameterBanks { cache, .. } => {
578 ParameterBankResidency::IndependentCache(cache)
579 }
580 }
581 }
582
583 pub const fn parameter_bank_cache(self) -> Option<ParameterBankLoadOptions> {
585 match self {
586 Self::Layers(_) => None,
587 Self::IndependentParameterBanks { cache, .. } => Some(cache),
588 }
589 }
590
591 pub const fn ordinary_residency(self) -> Option<OrdinaryWeightResidency> {
593 match self {
594 Self::Layers(_) => None,
595 Self::IndependentParameterBanks { ordinary, .. } => Some(ordinary),
596 }
597 }
598
599 pub const fn ordinary_is_fully_resident(self) -> bool {
601 matches!(
602 self,
603 Self::Layers(LayerWeightResidency::FullyResident)
604 | Self::IndependentParameterBanks {
605 ordinary: OrdinaryWeightResidency::FullyResident,
606 ..
607 }
608 )
609 }
610
611 pub const fn is_fully_resident(self) -> bool {
613 matches!(self, Self::Layers(LayerWeightResidency::FullyResident))
614 }
615
616 pub const fn max_cached_shards(self) -> usize {
618 self.layers().max_cached_shards()
619 }
620}
621
622impl Default for WeightResidency {
623 fn default() -> Self {
624 Self::fully_resident()
625 }
626}
627
628impl LayerWeightResidency {
629 pub const fn max_cached_shards(self) -> usize {
631 match self {
632 Self::FullyResident => DEFAULT_MAX_CACHED_SHARDS,
633 Self::LayerwiseHost(options) => options.max_cached_shards,
634 Self::DenseDiskStream(options) => options.max_cached_shards,
635 }
636 }
637
638 pub const fn sample_backend_memory(self) -> bool {
640 match self {
641 Self::FullyResident => false,
642 Self::LayerwiseHost(options) => options.sample_backend_memory,
643 Self::DenseDiskStream(options) => options.sample_backend_memory,
644 }
645 }
646
647 pub const fn sample_process_memory(self) -> bool {
649 match self {
650 Self::FullyResident => false,
651 Self::LayerwiseHost(options) => options.sample_process_memory,
652 Self::DenseDiskStream(options) => options.sample_process_memory,
653 }
654 }
655
656 pub fn device_depth(self, unit_count: usize) -> usize {
658 match self {
659 Self::FullyResident => unit_count,
660 Self::LayerwiseHost(options) => options.offload.prefetch_depth(),
661 Self::DenseDiskStream(_) => unit_count.min(DENSE_TRANSFER_WINDOW),
662 }
663 }
664
665 pub fn offload(self) -> Result<OffloadConfig, WeightResidencyPolicyError> {
667 match self {
668 Self::FullyResident => Ok(OffloadConfig::default()),
669 Self::LayerwiseHost(options) => Ok(options.offload),
670 Self::DenseDiskStream(options) => {
671 options.validate()?;
672 Ok(OffloadConfig::new(
673 Some(options.device_budget_bytes),
674 Some(options.host_budget_bytes),
675 options.host_lookahead.max(DENSE_TRANSFER_WINDOW),
676 )?
677 .with_eviction_policy(options.eviction_policy))
678 }
679 }
680 }
681
682 pub const fn dense(self) -> Option<DenseDiskStreamLoadOptions> {
684 match self {
685 Self::DenseDiskStream(options) => Some(options),
686 Self::FullyResident | Self::LayerwiseHost(_) => None,
687 }
688 }
689
690 pub const fn is_fully_resident(self) -> bool {
692 matches!(self, Self::FullyResident)
693 }
694
695 pub const fn execution_residency(self) -> ExecutionResidency {
697 match self {
698 Self::FullyResident => ExecutionResidency::FullyResident,
699 Self::LayerwiseHost(_) => ExecutionResidency::LayerwiseHost,
700 Self::DenseDiskStream(_) => ExecutionResidency::DenseDiskStream,
701 }
702 }
703}
704
705impl From<LayerwiseLoadOptions> for LayerWeightResidency {
706 fn from(value: LayerwiseLoadOptions) -> Self {
707 Self::LayerwiseHost(value)
708 }
709}
710
711impl From<DenseDiskStreamLoadOptions> for LayerWeightResidency {
712 fn from(value: DenseDiskStreamLoadOptions) -> Self {
713 Self::DenseDiskStream(value)
714 }
715}
716
717#[derive(Debug, Clone, Copy, Eq, PartialEq)]
719#[non_exhaustive]
720pub enum ExecutionResidency {
721 FullyResident,
723 LayerwiseHost,
725 DenseDiskStream,
727}
728
729#[derive(Debug, Clone, Eq, PartialEq)]
731pub struct LayerwiseModelMetadata {
732 effective_model_type: String,
733 quantization: Option<eredu_checkpoint::WeightQuantization>,
734 layer_count: usize,
735 static_device_bytes: u64,
736 residency: ExecutionResidency,
737 layer_parameter_bytes: u64,
738 maximum_device_layer_bytes: u64,
739 maximum_host_layer_bytes: u64,
740 device_layer_capacity: usize,
741 materialization: Option<crate::WeightMaterializationReport>,
742}
743
744impl LayerwiseModelMetadata {
745 #[allow(clippy::too_many_arguments)]
747 pub fn new(
748 effective_model_type: impl Into<String>,
749 quantization: Option<eredu_checkpoint::WeightQuantization>,
750 layer_count: usize,
751 static_device_bytes: u64,
752 residency: ExecutionResidency,
753 layer_parameter_bytes: u64,
754 maximum_device_layer_bytes: u64,
755 maximum_host_layer_bytes: u64,
756 device_layer_capacity: usize,
757 ) -> Self {
758 Self {
759 effective_model_type: effective_model_type.into(),
760 quantization,
761 layer_count,
762 static_device_bytes,
763 residency,
764 layer_parameter_bytes,
765 maximum_device_layer_bytes,
766 maximum_host_layer_bytes,
767 device_layer_capacity,
768 materialization: None,
769 }
770 }
771
772 pub fn set_effective_model_type(&mut self, effective_model_type: impl Into<String>) {
774 self.effective_model_type = effective_model_type.into();
775 }
776
777 pub fn set_quantization(&mut self, quantization: Option<eredu_checkpoint::WeightQuantization>) {
779 self.quantization = quantization;
780 }
781
782 pub fn set_materialization(
784 &mut self,
785 materialization: Option<crate::WeightMaterializationReport>,
786 ) {
787 self.materialization = materialization;
788 }
789
790 pub fn effective_model_type(&self) -> &str {
792 &self.effective_model_type
793 }
794
795 pub const fn quantization(&self) -> Option<eredu_checkpoint::WeightQuantization> {
797 self.quantization
798 }
799
800 pub const fn layer_count(&self) -> usize {
802 self.layer_count
803 }
804
805 pub const fn static_device_bytes(&self) -> u64 {
807 self.static_device_bytes
808 }
809
810 pub const fn residency(&self) -> ExecutionResidency {
812 self.residency
813 }
814
815 pub const fn layer_parameter_bytes(&self) -> u64 {
817 self.layer_parameter_bytes
818 }
819
820 pub const fn maximum_device_layer_bytes(&self) -> u64 {
822 self.maximum_device_layer_bytes
823 }
824
825 pub const fn maximum_host_layer_bytes(&self) -> u64 {
827 self.maximum_host_layer_bytes
828 }
829
830 pub const fn device_layer_capacity(&self) -> usize {
832 self.device_layer_capacity
833 }
834
835 pub const fn materialization(&self) -> Option<&crate::WeightMaterializationReport> {
837 self.materialization.as_ref()
838 }
839}
840
841#[derive(Debug, thiserror::Error)]
843#[non_exhaustive]
844pub enum WeightResidencyPolicyError {
845 #[error("dense disk streaming host lookahead must be nonzero when the host budget is enabled")]
847 ZeroHostLookahead,
848 #[error("dense disk streaming background queue capacity must be nonzero when host caching is enabled")]
850 ZeroQueueCapacity,
851 #[error("dense disk streaming with a zero host budget requires zero host lookahead and queue capacity")]
853 HostDisabledControls,
854 #[error("parameter-bank compact scratch limit must be nonzero")]
856 ZeroParameterBankScratchLimit,
857 #[error("parameter-bank prefill target must be nonzero")]
859 ZeroParameterBankPrefillTarget,
860 #[error("parameter-bank prefill target {target_bytes} exceeds scratch limit {scratch_bytes}")]
862 ParameterBankPrefillTargetExceedsScratch {
863 target_bytes: u64,
865 scratch_bytes: u64,
867 },
868 #[error(transparent)]
870 Offload(#[from] OffloadError),
871}
872
873#[cfg(test)]
874mod tests {
875 use super::*;
876
877 #[test]
878 fn static_unit_bindings_are_runtime_owned_and_decomposable() {
879 let binding = WeightBinding::new(
880 "embedding",
881 "model.embedding.weight",
882 eredu_checkpoint::store::TensorSelection::Full,
883 16,
884 )
885 .unwrap();
886 let unit = StaticUnitBindings::new("static.embedding", vec![binding.clone()]).unwrap();
887
888 assert_eq!(unit.id().as_str(), "static.embedding");
889 assert_eq!(unit.bindings(), std::slice::from_ref(&binding));
890 let (id, bindings) = unit.into_parts();
891 assert_eq!(id.as_str(), "static.embedding");
892 assert_eq!(bindings, vec![binding]);
893 }
894
895 #[test]
896 fn dense_stream_controls_fail_closed_without_a_backend() {
897 assert!(matches!(
898 DenseDiskStreamLoadOptions::new(1, 1, 0, 1),
899 Err(WeightResidencyPolicyError::ZeroHostLookahead)
900 ));
901 assert!(matches!(
902 DenseDiskStreamLoadOptions::new(1, 1, 1, 0),
903 Err(WeightResidencyPolicyError::ZeroQueueCapacity)
904 ));
905 assert!(matches!(
906 DenseDiskStreamLoadOptions::new(1, 0, 1, 0),
907 Err(WeightResidencyPolicyError::HostDisabledControls)
908 ));
909 assert!(DenseDiskStreamLoadOptions::new(1, 0, 0, 0).is_ok());
910 }
911
912 #[test]
913 fn residency_policy_derives_finite_dense_windows() {
914 let options = DenseDiskStreamLoadOptions::new(32, 64, 3, 2).unwrap();
915 let policy = LayerWeightResidency::DenseDiskStream(options);
916 assert_eq!(policy.device_depth(8), DENSE_TRANSFER_WINDOW);
917 let offload = policy.offload().unwrap();
918 assert_eq!(offload.device_budget_bytes(), Some(32));
919 assert_eq!(offload.host_budget_bytes(), Some(64));
920 assert_eq!(offload.prefetch_depth(), 3);
921 }
922
923 #[test]
924 fn dense_transfer_schedule_preserves_order_and_bounded_lookahead() {
925 let mut schedule = DenseTransferSchedule::new(3..7, 2).unwrap();
926 assert_eq!(schedule.desired_indices(3), vec![3, 4, 5]);
927 assert_eq!(
928 schedule.admit(4, "wrong"),
929 Err(DenseTransferScheduleError::OutOfOrder {
930 expected: 3,
931 actual: 4,
932 })
933 );
934 schedule.admit(3, "three").unwrap();
935 schedule.admit(4, "four").unwrap();
936 assert!(!schedule.can_admit());
937 assert_eq!(schedule.desired_indices(3), vec![3, 4, 5]);
938 assert_eq!(schedule.pop_ready(), Some((3, "three")));
939 schedule.admit(5, "five").unwrap();
940 assert_eq!(schedule.desired_indices(4), vec![4, 5, 6]);
941 assert_eq!(schedule.pop_ready(), Some((4, "four")));
942 assert_eq!(schedule.pop_ready(), Some((5, "five")));
943 schedule.admit(6, "six").unwrap();
944 assert_eq!(schedule.pop_ready(), Some((6, "six")));
945 assert!(schedule.is_exhausted());
946 }
947
948 #[test]
949 fn expert_cache_controls_and_composite_placement_are_backend_neutral() {
950 let experts = ParameterBankLoadOptions::new(OffloadConfig::default(), 64, 32).unwrap();
951 let placement = WeightResidency::with_independent_parameter_banks(
952 OrdinaryWeightResidency::FullyResident,
953 experts,
954 );
955 assert_eq!(placement.parameter_bank_cache(), Some(experts));
956 assert!(placement.ordinary_is_fully_resident());
957 assert!(!placement.is_fully_resident());
958 assert!(matches!(
959 ParameterBankLoadOptions::new(OffloadConfig::default(), 64, 0),
960 Err(WeightResidencyPolicyError::ZeroParameterBankPrefillTarget)
961 ));
962 assert_eq!(
963 ParameterBankKey::new(3, 7).unit_id().as_str(),
964 "bank.unit.00003.member.00007"
965 );
966 }
967
968 #[test]
969 fn layerwise_metadata_is_runtime_owned_and_updateable() {
970 let mut metadata = LayerwiseModelMetadata::new(
971 "generic",
972 None,
973 4,
974 10,
975 ExecutionResidency::LayerwiseHost,
976 20,
977 8,
978 6,
979 2,
980 );
981 metadata.set_effective_model_type("llama");
982 metadata.set_quantization(Some(eredu_checkpoint::WeightQuantization::Affine(
983 eredu_checkpoint::AffineQuantization::default(),
984 )));
985
986 assert_eq!(metadata.effective_model_type(), "llama");
987 assert_eq!(metadata.layer_count(), 4);
988 assert_eq!(metadata.static_device_bytes(), 10);
989 assert_eq!(metadata.layer_parameter_bytes(), 20);
990 assert_eq!(metadata.maximum_device_layer_bytes(), 8);
991 assert_eq!(metadata.maximum_host_layer_bytes(), 6);
992 assert_eq!(metadata.device_layer_capacity(), 2);
993 }
994}