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, Hash, Ord, PartialEq, PartialOrd)]
412#[non_exhaustive]
413pub enum ParameterBankAccess {
414 Bulk,
416 Incremental,
418}
419
420impl ExpertPass {
421 pub const fn parameter_bank_access(self) -> ParameterBankAccess {
423 match self {
424 Self::Prefill => ParameterBankAccess::Bulk,
425 Self::Decode => ParameterBankAccess::Incremental,
426 }
427 }
428}
429
430#[derive(Debug, Clone, Copy, Eq, PartialEq)]
432pub struct ParameterBankLoadOptions {
433 members: OffloadConfig,
435 compact_bank_scratch_bytes: u64,
437 prefill_compact_bank_target_bytes: u64,
439}
440
441impl ParameterBankLoadOptions {
442 pub fn new(
444 members: OffloadConfig,
445 compact_bank_scratch_bytes: u64,
446 prefill_compact_bank_target_bytes: u64,
447 ) -> Result<Self, WeightResidencyPolicyError> {
448 let options = Self {
449 members,
450 compact_bank_scratch_bytes,
451 prefill_compact_bank_target_bytes,
452 };
453 options.validate()?;
454 Ok(options)
455 }
456
457 pub fn validate(self) -> Result<(), WeightResidencyPolicyError> {
459 if self.compact_bank_scratch_bytes == 0 {
460 return Err(WeightResidencyPolicyError::ZeroParameterBankScratchLimit);
461 }
462 if self.prefill_compact_bank_target_bytes == 0 {
463 return Err(WeightResidencyPolicyError::ZeroParameterBankPrefillTarget);
464 }
465 if self.prefill_compact_bank_target_bytes > self.compact_bank_scratch_bytes {
466 return Err(
467 WeightResidencyPolicyError::ParameterBankPrefillTargetExceedsScratch {
468 target_bytes: self.prefill_compact_bank_target_bytes,
469 scratch_bytes: self.compact_bank_scratch_bytes,
470 },
471 );
472 }
473 Ok(())
474 }
475
476 pub const fn offload(self) -> OffloadConfig {
478 self.members
479 }
480 pub const fn compact_bank_scratch_bytes(self) -> u64 {
482 self.compact_bank_scratch_bytes
483 }
484 pub const fn prefill_compact_bank_target_bytes(self) -> u64 {
486 self.prefill_compact_bank_target_bytes
487 }
488}
489
490impl Default for ParameterBankLoadOptions {
491 fn default() -> Self {
492 Self {
493 members: OffloadConfig::default(),
494 compact_bank_scratch_bytes: u64::MAX,
495 prefill_compact_bank_target_bytes: 1 << 30,
496 }
497 }
498}
499
500#[derive(Debug, Clone, Copy, Eq, PartialEq)]
502#[non_exhaustive]
503pub enum OrdinaryWeightResidency {
504 FullyResident,
506 LayerwiseHost(LayerwiseLoadOptions),
508 DenseDiskStream(DenseDiskStreamLoadOptions),
510}
511
512impl OrdinaryWeightResidency {
513 pub const fn layers(self) -> LayerWeightResidency {
515 match self {
516 Self::FullyResident => LayerWeightResidency::FullyResident,
517 Self::LayerwiseHost(options) => LayerWeightResidency::LayerwiseHost(options),
518 Self::DenseDiskStream(options) => LayerWeightResidency::DenseDiskStream(options),
519 }
520 }
521}
522
523impl From<OrdinaryWeightResidency> for LayerWeightResidency {
524 fn from(value: OrdinaryWeightResidency) -> Self {
525 value.layers()
526 }
527}
528
529#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
531#[non_exhaustive]
532pub enum ParameterBankResidency {
533 #[default]
535 WithLayer,
536 IndependentCache(ParameterBankLoadOptions),
538}
539
540#[derive(Debug, Clone, Copy, Eq, PartialEq)]
542#[non_exhaustive]
543pub enum WeightResidency {
544 Layers(LayerWeightResidency),
546 #[non_exhaustive]
548 IndependentParameterBanks {
549 ordinary: OrdinaryWeightResidency,
551 cache: ParameterBankLoadOptions,
553 },
554}
555
556impl WeightResidency {
557 pub const fn fully_resident() -> Self {
559 Self::with_layers(LayerWeightResidency::FullyResident)
560 }
561
562 pub const fn layerwise_host(options: LayerwiseLoadOptions) -> Self {
564 Self::with_layers(LayerWeightResidency::LayerwiseHost(options))
565 }
566
567 pub const fn dense_disk_stream(options: DenseDiskStreamLoadOptions) -> Self {
569 Self::with_layers(LayerWeightResidency::DenseDiskStream(options))
570 }
571
572 pub const fn with_layers(layers: LayerWeightResidency) -> Self {
574 Self::Layers(layers)
575 }
576
577 pub const fn with_independent_parameter_banks(
579 ordinary: OrdinaryWeightResidency,
580 cache: ParameterBankLoadOptions,
581 ) -> Self {
582 Self::IndependentParameterBanks { ordinary, cache }
583 }
584
585 pub const fn layers(self) -> LayerWeightResidency {
587 match self {
588 Self::Layers(layers) => layers,
589 Self::IndependentParameterBanks { ordinary, .. } => ordinary.layers(),
590 }
591 }
592
593 pub const fn parameter_banks(self) -> ParameterBankResidency {
595 match self {
596 Self::Layers(_) => ParameterBankResidency::WithLayer,
597 Self::IndependentParameterBanks { cache, .. } => {
598 ParameterBankResidency::IndependentCache(cache)
599 }
600 }
601 }
602
603 pub const fn parameter_bank_cache(self) -> Option<ParameterBankLoadOptions> {
605 match self {
606 Self::Layers(_) => None,
607 Self::IndependentParameterBanks { cache, .. } => Some(cache),
608 }
609 }
610
611 pub const fn ordinary_residency(self) -> Option<OrdinaryWeightResidency> {
613 match self {
614 Self::Layers(_) => None,
615 Self::IndependentParameterBanks { ordinary, .. } => Some(ordinary),
616 }
617 }
618
619 pub const fn ordinary_is_fully_resident(self) -> bool {
621 matches!(
622 self,
623 Self::Layers(LayerWeightResidency::FullyResident)
624 | Self::IndependentParameterBanks {
625 ordinary: OrdinaryWeightResidency::FullyResident,
626 ..
627 }
628 )
629 }
630
631 pub const fn is_fully_resident(self) -> bool {
633 matches!(self, Self::Layers(LayerWeightResidency::FullyResident))
634 }
635
636 pub const fn max_cached_shards(self) -> usize {
638 self.layers().max_cached_shards()
639 }
640}
641
642impl Default for WeightResidency {
643 fn default() -> Self {
644 Self::fully_resident()
645 }
646}
647
648impl LayerWeightResidency {
649 pub const fn max_cached_shards(self) -> usize {
651 match self {
652 Self::FullyResident => DEFAULT_MAX_CACHED_SHARDS,
653 Self::LayerwiseHost(options) => options.max_cached_shards,
654 Self::DenseDiskStream(options) => options.max_cached_shards,
655 }
656 }
657
658 pub const fn sample_backend_memory(self) -> bool {
660 match self {
661 Self::FullyResident => false,
662 Self::LayerwiseHost(options) => options.sample_backend_memory,
663 Self::DenseDiskStream(options) => options.sample_backend_memory,
664 }
665 }
666
667 pub const fn sample_process_memory(self) -> bool {
669 match self {
670 Self::FullyResident => false,
671 Self::LayerwiseHost(options) => options.sample_process_memory,
672 Self::DenseDiskStream(options) => options.sample_process_memory,
673 }
674 }
675
676 pub fn device_depth(self, unit_count: usize) -> usize {
678 match self {
679 Self::FullyResident => unit_count,
680 Self::LayerwiseHost(options) => options.offload.prefetch_depth(),
681 Self::DenseDiskStream(_) => unit_count.min(DENSE_TRANSFER_WINDOW),
682 }
683 }
684
685 pub fn offload(self) -> Result<OffloadConfig, WeightResidencyPolicyError> {
687 match self {
688 Self::FullyResident => Ok(OffloadConfig::default()),
689 Self::LayerwiseHost(options) => Ok(options.offload),
690 Self::DenseDiskStream(options) => {
691 options.validate()?;
692 Ok(OffloadConfig::new(
693 Some(options.device_budget_bytes),
694 Some(options.host_budget_bytes),
695 options.host_lookahead.max(DENSE_TRANSFER_WINDOW),
696 )?
697 .with_eviction_policy(options.eviction_policy))
698 }
699 }
700 }
701
702 pub const fn dense(self) -> Option<DenseDiskStreamLoadOptions> {
704 match self {
705 Self::DenseDiskStream(options) => Some(options),
706 Self::FullyResident | Self::LayerwiseHost(_) => None,
707 }
708 }
709
710 pub const fn is_fully_resident(self) -> bool {
712 matches!(self, Self::FullyResident)
713 }
714
715 pub const fn execution_residency(self) -> ExecutionResidency {
717 match self {
718 Self::FullyResident => ExecutionResidency::FullyResident,
719 Self::LayerwiseHost(_) => ExecutionResidency::LayerwiseHost,
720 Self::DenseDiskStream(_) => ExecutionResidency::DenseDiskStream,
721 }
722 }
723}
724
725impl From<LayerwiseLoadOptions> for LayerWeightResidency {
726 fn from(value: LayerwiseLoadOptions) -> Self {
727 Self::LayerwiseHost(value)
728 }
729}
730
731impl From<DenseDiskStreamLoadOptions> for LayerWeightResidency {
732 fn from(value: DenseDiskStreamLoadOptions) -> Self {
733 Self::DenseDiskStream(value)
734 }
735}
736
737#[derive(Debug, Clone, Copy, Eq, PartialEq)]
739#[non_exhaustive]
740pub enum ExecutionResidency {
741 FullyResident,
743 LayerwiseHost,
745 DenseDiskStream,
747}
748
749#[derive(Debug, Clone, Eq, PartialEq)]
751pub struct LayerwiseModelMetadata {
752 effective_model_type: String,
753 quantization: Option<eredu_checkpoint::WeightQuantization>,
754 layer_count: usize,
755 static_device_bytes: u64,
756 residency: ExecutionResidency,
757 layer_parameter_bytes: u64,
758 maximum_device_layer_bytes: u64,
759 maximum_host_layer_bytes: u64,
760 device_layer_capacity: usize,
761 materialization: Option<crate::WeightMaterializationReport>,
762}
763
764impl LayerwiseModelMetadata {
765 #[allow(clippy::too_many_arguments)]
767 pub fn new(
768 effective_model_type: impl Into<String>,
769 quantization: Option<eredu_checkpoint::WeightQuantization>,
770 layer_count: usize,
771 static_device_bytes: u64,
772 residency: ExecutionResidency,
773 layer_parameter_bytes: u64,
774 maximum_device_layer_bytes: u64,
775 maximum_host_layer_bytes: u64,
776 device_layer_capacity: usize,
777 ) -> Self {
778 Self {
779 effective_model_type: effective_model_type.into(),
780 quantization,
781 layer_count,
782 static_device_bytes,
783 residency,
784 layer_parameter_bytes,
785 maximum_device_layer_bytes,
786 maximum_host_layer_bytes,
787 device_layer_capacity,
788 materialization: None,
789 }
790 }
791
792 pub fn set_effective_model_type(&mut self, effective_model_type: impl Into<String>) {
794 self.effective_model_type = effective_model_type.into();
795 }
796
797 pub fn set_quantization(&mut self, quantization: Option<eredu_checkpoint::WeightQuantization>) {
799 self.quantization = quantization;
800 }
801
802 pub fn set_materialization(
804 &mut self,
805 materialization: Option<crate::WeightMaterializationReport>,
806 ) {
807 self.materialization = materialization;
808 }
809
810 pub fn effective_model_type(&self) -> &str {
812 &self.effective_model_type
813 }
814
815 pub const fn quantization(&self) -> Option<eredu_checkpoint::WeightQuantization> {
817 self.quantization
818 }
819
820 pub const fn layer_count(&self) -> usize {
822 self.layer_count
823 }
824
825 pub const fn static_device_bytes(&self) -> u64 {
827 self.static_device_bytes
828 }
829
830 pub const fn residency(&self) -> ExecutionResidency {
832 self.residency
833 }
834
835 pub const fn layer_parameter_bytes(&self) -> u64 {
837 self.layer_parameter_bytes
838 }
839
840 pub const fn maximum_device_layer_bytes(&self) -> u64 {
842 self.maximum_device_layer_bytes
843 }
844
845 pub const fn maximum_host_layer_bytes(&self) -> u64 {
847 self.maximum_host_layer_bytes
848 }
849
850 pub const fn device_layer_capacity(&self) -> usize {
852 self.device_layer_capacity
853 }
854
855 pub const fn materialization(&self) -> Option<&crate::WeightMaterializationReport> {
857 self.materialization.as_ref()
858 }
859}
860
861#[derive(Debug, thiserror::Error)]
863#[non_exhaustive]
864pub enum WeightResidencyPolicyError {
865 #[error("dense disk streaming host lookahead must be nonzero when the host budget is enabled")]
867 ZeroHostLookahead,
868 #[error("dense disk streaming background queue capacity must be nonzero when host caching is enabled")]
870 ZeroQueueCapacity,
871 #[error("dense disk streaming with a zero host budget requires zero host lookahead and queue capacity")]
873 HostDisabledControls,
874 #[error("parameter-bank compact scratch limit must be nonzero")]
876 ZeroParameterBankScratchLimit,
877 #[error("parameter-bank prefill target must be nonzero")]
879 ZeroParameterBankPrefillTarget,
880 #[error("parameter-bank prefill target {target_bytes} exceeds scratch limit {scratch_bytes}")]
882 ParameterBankPrefillTargetExceedsScratch {
883 target_bytes: u64,
885 scratch_bytes: u64,
887 },
888 #[error(transparent)]
890 Offload(#[from] OffloadError),
891}
892
893#[cfg(test)]
894mod tests {
895 use super::*;
896
897 #[test]
898 fn static_unit_bindings_are_runtime_owned_and_decomposable() {
899 let binding = WeightBinding::new(
900 "embedding",
901 "model.embedding.weight",
902 eredu_checkpoint::store::TensorSelection::Full,
903 16,
904 )
905 .unwrap();
906 let unit = StaticUnitBindings::new("static.embedding", vec![binding.clone()]).unwrap();
907
908 assert_eq!(unit.id().as_str(), "static.embedding");
909 assert_eq!(unit.bindings(), std::slice::from_ref(&binding));
910 let (id, bindings) = unit.into_parts();
911 assert_eq!(id.as_str(), "static.embedding");
912 assert_eq!(bindings, vec![binding]);
913 }
914
915 #[test]
916 fn dense_stream_controls_fail_closed_without_a_backend() {
917 assert!(matches!(
918 DenseDiskStreamLoadOptions::new(1, 1, 0, 1),
919 Err(WeightResidencyPolicyError::ZeroHostLookahead)
920 ));
921 assert!(matches!(
922 DenseDiskStreamLoadOptions::new(1, 1, 1, 0),
923 Err(WeightResidencyPolicyError::ZeroQueueCapacity)
924 ));
925 assert!(matches!(
926 DenseDiskStreamLoadOptions::new(1, 0, 1, 0),
927 Err(WeightResidencyPolicyError::HostDisabledControls)
928 ));
929 assert!(DenseDiskStreamLoadOptions::new(1, 0, 0, 0).is_ok());
930 }
931
932 #[test]
933 fn residency_policy_derives_finite_dense_windows() {
934 let options = DenseDiskStreamLoadOptions::new(32, 64, 3, 2).unwrap();
935 let policy = LayerWeightResidency::DenseDiskStream(options);
936 assert_eq!(policy.device_depth(8), DENSE_TRANSFER_WINDOW);
937 let offload = policy.offload().unwrap();
938 assert_eq!(offload.device_budget_bytes(), Some(32));
939 assert_eq!(offload.host_budget_bytes(), Some(64));
940 assert_eq!(offload.prefetch_depth(), 3);
941 }
942
943 #[test]
944 fn dense_transfer_schedule_preserves_order_and_bounded_lookahead() {
945 let mut schedule = DenseTransferSchedule::new(3..7, 2).unwrap();
946 assert_eq!(schedule.desired_indices(3), vec![3, 4, 5]);
947 assert_eq!(
948 schedule.admit(4, "wrong"),
949 Err(DenseTransferScheduleError::OutOfOrder {
950 expected: 3,
951 actual: 4,
952 })
953 );
954 schedule.admit(3, "three").unwrap();
955 schedule.admit(4, "four").unwrap();
956 assert!(!schedule.can_admit());
957 assert_eq!(schedule.desired_indices(3), vec![3, 4, 5]);
958 assert_eq!(schedule.pop_ready(), Some((3, "three")));
959 schedule.admit(5, "five").unwrap();
960 assert_eq!(schedule.desired_indices(4), vec![4, 5, 6]);
961 assert_eq!(schedule.pop_ready(), Some((4, "four")));
962 assert_eq!(schedule.pop_ready(), Some((5, "five")));
963 schedule.admit(6, "six").unwrap();
964 assert_eq!(schedule.pop_ready(), Some((6, "six")));
965 assert!(schedule.is_exhausted());
966 }
967
968 #[test]
969 fn expert_cache_controls_and_composite_placement_are_backend_neutral() {
970 let experts = ParameterBankLoadOptions::new(OffloadConfig::default(), 64, 32).unwrap();
971 let placement = WeightResidency::with_independent_parameter_banks(
972 OrdinaryWeightResidency::FullyResident,
973 experts,
974 );
975 assert_eq!(placement.parameter_bank_cache(), Some(experts));
976 assert!(placement.ordinary_is_fully_resident());
977 assert!(!placement.is_fully_resident());
978 assert!(matches!(
979 ParameterBankLoadOptions::new(OffloadConfig::default(), 64, 0),
980 Err(WeightResidencyPolicyError::ZeroParameterBankPrefillTarget)
981 ));
982 assert_eq!(
983 ParameterBankKey::new(3, 7).unit_id().as_str(),
984 "bank.unit.00003.member.00007"
985 );
986 }
987
988 #[test]
989 fn layerwise_metadata_is_runtime_owned_and_updateable() {
990 let mut metadata = LayerwiseModelMetadata::new(
991 "generic",
992 None,
993 4,
994 10,
995 ExecutionResidency::LayerwiseHost,
996 20,
997 8,
998 6,
999 2,
1000 );
1001 metadata.set_effective_model_type("llama");
1002 metadata.set_quantization(Some(eredu_checkpoint::WeightQuantization::Affine(
1003 eredu_checkpoint::AffineQuantization::default(),
1004 )));
1005
1006 assert_eq!(metadata.effective_model_type(), "llama");
1007 assert_eq!(metadata.layer_count(), 4);
1008 assert_eq!(metadata.static_device_bytes(), 10);
1009 assert_eq!(metadata.layer_parameter_bytes(), 20);
1010 assert_eq!(metadata.maximum_device_layer_bytes(), 8);
1011 assert_eq!(metadata.maximum_host_layer_bytes(), 6);
1012 assert_eq!(metadata.device_layer_capacity(), 2);
1013 }
1014}