1use std::{marker::PhantomData, ops::Range};
8
9use eredu_core::{
10 cache::{
11 LayerCachePolicy, PromptCacheError, PromptCacheModelIdentity, PromptCacheStateSegment,
12 PromptCacheTopology, StateComponentPolicy, StateTensorRole,
13 },
14 LayerSchedule,
15};
16use eredu_nn::NeuralBackend;
17
18#[derive(Debug, Clone, Copy, Eq, PartialEq)]
20#[non_exhaustive]
21pub enum ArchitectureStatePlacement {
22 GroupUnits {
24 group: usize,
26 },
27 OutputOwner,
29}
30
31#[derive(Debug, Clone, Eq, PartialEq)]
33pub struct ArchitectureStatePartitionRule {
34 layers: Range<usize>,
35 placement: ArchitectureStatePlacement,
36}
37
38impl ArchitectureStatePartitionRule {
39 pub fn group_units(group: usize, layers: Range<usize>) -> Self {
41 Self {
42 layers,
43 placement: ArchitectureStatePlacement::GroupUnits { group },
44 }
45 }
46
47 pub fn output_owner(layers: Range<usize>) -> Self {
49 Self {
50 layers,
51 placement: ArchitectureStatePlacement::OutputOwner,
52 }
53 }
54
55 pub fn layers(&self) -> Range<usize> {
57 self.layers.clone()
58 }
59
60 pub const fn placement(&self) -> ArchitectureStatePlacement {
62 self.placement
63 }
64}
65
66#[derive(Debug, Clone, Eq, PartialEq)]
71pub struct ArchitectureStatePartitionPlan {
72 rules: Vec<ArchitectureStatePartitionRule>,
73}
74
75impl ArchitectureStatePartitionPlan {
76 pub fn new(rules: impl IntoIterator<Item = ArchitectureStatePartitionRule>) -> Self {
78 Self {
79 rules: rules.into_iter().collect(),
80 }
81 }
82
83 pub fn rules(&self) -> &[ArchitectureStatePartitionRule] {
85 &self.rules
86 }
87}
88
89#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
91#[non_exhaustive]
92pub enum ArchitectureStatePartitionError {
93 #[error("architecture state partition plan must contain at least one rule")]
95 EmptyPlan,
96 #[error("architecture state partition rule has empty range {start}..{end}")]
98 EmptyRange {
99 start: usize,
101 end: usize,
103 },
104 #[error("architecture state partition range {start}..{end} exceeds the {layers}-layer layout")]
106 RangeOutOfBounds {
107 start: usize,
109 end: usize,
111 layers: usize,
113 },
114 #[error(
116 "architecture state partition range starts at {start}, before prior frontier {frontier}"
117 )]
118 OverlappingRange {
119 start: usize,
121 frontier: usize,
123 },
124 #[error("architecture state layer {layer} is not assigned by the partition plan")]
126 UnassignedLayer {
127 layer: usize,
129 },
130 #[error("architecture state partition references unknown execution group {group}")]
132 UnknownGroup {
133 group: usize,
135 },
136 #[error(
138 "architecture state range {start}..{end} has {} layers but group {group} has {units} units",
139 end - start
140 )]
141 GroupLengthMismatch {
142 group: usize,
144 start: usize,
146 end: usize,
148 units: usize,
150 },
151 #[error(
153 "architecture state partition selects discontiguous ranges ending at {frontier} and starting at {start}"
154 )]
155 DiscontiguousSelection {
156 frontier: usize,
158 start: usize,
160 },
161 #[error("architecture state partition layout is invalid: {0}")]
163 InvalidLayout(String),
164}
165
166pub const DEFAULT_STATE_SEGMENT_ID: &str = "state";
168
169#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
171pub struct StateSegmentId(String);
172
173impl StateSegmentId {
174 pub fn new(id: impl Into<String>) -> Result<Self, StateError> {
176 let id = id.into();
177 if id.trim().is_empty() {
178 return Err(StateError::EmptySegmentId);
179 }
180 Ok(Self(id))
181 }
182
183 pub fn as_str(&self) -> &str {
185 &self.0
186 }
187}
188
189impl std::fmt::Display for StateSegmentId {
190 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
191 formatter.write_str(&self.0)
192 }
193}
194
195#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
197#[non_exhaustive]
198pub enum StateSegmentLifetime {
199 Persistent,
201 FrameLocal,
203}
204
205#[derive(Debug, Clone, Eq, PartialEq)]
207pub struct StateSegmentSpec {
208 id: StateSegmentId,
209 layers: Range<usize>,
210 lifetime: StateSegmentLifetime,
211 processed_token_offset: i32,
212}
213
214impl StateSegmentSpec {
215 pub fn new(
217 id: impl Into<String>,
218 layers: Range<usize>,
219 lifetime: StateSegmentLifetime,
220 processed_token_offset: i32,
221 ) -> Result<Self, StateError> {
222 let id = StateSegmentId::new(id)?;
223 if layers.is_empty() {
224 return Err(StateError::EmptySegmentRange {
225 segment: id,
226 start: layers.start,
227 end: layers.end,
228 });
229 }
230 if processed_token_offset > 0 {
231 return Err(StateError::PositiveSegmentOffset {
232 segment: id,
233 offset: processed_token_offset,
234 });
235 }
236 Ok(Self {
237 id,
238 layers,
239 lifetime,
240 processed_token_offset,
241 })
242 }
243
244 pub const fn id(&self) -> &StateSegmentId {
246 &self.id
247 }
248
249 pub fn layers(&self) -> Range<usize> {
251 self.layers.clone()
252 }
253
254 pub const fn lifetime(&self) -> StateSegmentLifetime {
256 self.lifetime
257 }
258
259 pub const fn processed_token_offset(&self) -> i32 {
261 self.processed_token_offset
262 }
263}
264
265#[derive(Debug, Clone, Eq, PartialEq)]
267pub struct StateLayout {
268 layers: LayerSchedule<LayerCachePolicy>,
269 components: Vec<Vec<StateComponentPolicy>>,
270 segments: Vec<StateSegmentSpec>,
271}
272
273impl StateLayout {
274 pub fn new(layers: LayerSchedule<LayerCachePolicy>) -> Result<Self, StateError> {
277 if layers.is_empty() {
278 return Err(StateError::EmptyLayout);
279 }
280 let count = layers.len();
281 Self::segmented(
282 layers,
283 [StateSegmentSpec::new(
284 DEFAULT_STATE_SEGMENT_ID,
285 0..count,
286 StateSegmentLifetime::Persistent,
287 0,
288 )?],
289 )
290 }
291
292 pub fn segmented(
299 layers: LayerSchedule<LayerCachePolicy>,
300 segments: impl IntoIterator<Item = StateSegmentSpec>,
301 ) -> Result<Self, StateError> {
302 if layers.is_empty() {
303 return Err(StateError::EmptyLayout);
304 }
305 for (layer, policy) in layers.iter().enumerate() {
306 policy
307 .validate()
308 .map_err(|error| StateError::InvalidLayer {
309 layer,
310 reason: error.to_string(),
311 })?;
312 }
313 let components = layers.iter().map(LayerCachePolicy::components).collect();
314 let mut segments = segments.into_iter().collect::<Vec<_>>();
315 if segments.is_empty() {
316 return Err(StateError::EmptySegments);
317 }
318 segments.sort_by(|left, right| {
319 left.layers
320 .start
321 .cmp(&right.layers.start)
322 .then_with(|| left.layers.end.cmp(&right.layers.end))
323 .then_with(|| left.id.cmp(&right.id))
324 });
325 let mut identities = std::collections::BTreeSet::new();
326 let mut frontier = 0usize;
327 for segment in &segments {
328 if !identities.insert(segment.id.clone()) {
329 return Err(StateError::DuplicateSegment {
330 segment: segment.id.clone(),
331 });
332 }
333 if segment.layers.end > layers.len() {
334 return Err(StateError::SegmentOutOfBounds {
335 segment: segment.id.clone(),
336 start: segment.layers.start,
337 end: segment.layers.end,
338 layers: layers.len(),
339 });
340 }
341 if segment.layers.start < frontier {
342 return Err(StateError::OverlappingSegment {
343 segment: segment.id.clone(),
344 start: segment.layers.start,
345 frontier,
346 });
347 }
348 if segment.layers.start > frontier {
349 return Err(StateError::UnassignedStateLayer { layer: frontier });
350 }
351 frontier = segment.layers.end;
352 }
353 if frontier != layers.len() {
354 return Err(StateError::UnassignedStateLayer { layer: frontier });
355 }
356 Ok(Self {
357 layers,
358 components,
359 segments,
360 })
361 }
362
363 pub fn len(&self) -> usize {
365 self.layers.len()
366 }
367
368 pub fn logical_metadata_bytes(&self) -> Option<u64> {
372 use eredu_core::cache::{StateTensorDimension, StateTensorPolicy};
373 fn bytes<T>(count: usize) -> Option<u64> {
374 u64::try_from(std::mem::size_of::<T>().checked_mul(count)?).ok()
375 }
376 let mut total = bytes::<Self>(1)?
377 .checked_add(bytes::<LayerCachePolicy>(self.layers.len())?)?
378 .checked_add(bytes::<Vec<StateComponentPolicy>>(self.components.len())?)?
379 .checked_add(bytes::<StateSegmentSpec>(self.segments.len())?)?;
380 for layer in self.layers.iter() {
381 total = total.checked_add(bytes::<StateTensorPolicy>(layer.fixed_state().len())?)?;
382 for tensor in layer.fixed_state() {
383 total = total.checked_add(bytes::<StateTensorDimension>(tensor.shape.len())?)?;
384 }
385 }
386 for components in &self.components {
387 total = total.checked_add(bytes::<StateComponentPolicy>(components.len())?)?;
388 for component in components {
389 total =
390 total.checked_add(bytes::<StateTensorDimension>(component.shape().len())?)?;
391 }
392 }
393 for segment in &self.segments {
394 total = total.checked_add(u64::try_from(segment.id().as_str().len()).ok()?)?;
395 }
396 Some(total)
397 }
398
399 pub fn is_empty(&self) -> bool {
401 self.layers.is_empty()
402 }
403
404 pub fn layer(&self, layer: usize) -> Option<&LayerCachePolicy> {
406 self.layers.get(layer)
407 }
408
409 pub const fn layers(&self) -> &LayerSchedule<LayerCachePolicy> {
411 &self.layers
412 }
413
414 pub fn components(&self, layer: usize) -> Option<&[StateComponentPolicy]> {
416 self.components.get(layer).map(Vec::as_slice)
417 }
418
419 pub fn segments(&self) -> &[StateSegmentSpec] {
421 &self.segments
422 }
423
424 pub fn segment(&self, id: &StateSegmentId) -> Option<&StateSegmentSpec> {
426 self.segments.iter().find(|segment| segment.id() == id)
427 }
428
429 pub fn segment_for_layer(&self, layer: usize) -> Option<&StateSegmentSpec> {
431 self.segments
432 .iter()
433 .find(|segment| segment.layers.contains(&layer))
434 }
435
436 pub fn layer_prefix_offsets(&self) -> Vec<i32> {
438 let mut offsets = Vec::with_capacity(self.len());
439 for segment in &self.segments {
440 offsets.extend(std::iter::repeat_n(
441 segment.processed_token_offset(),
442 segment.layers.len(),
443 ));
444 }
445 offsets
446 }
447
448 pub fn slice(&self, layers: Range<usize>) -> Result<Self, StateError> {
451 if layers.is_empty() || layers.end > self.len() {
452 return Err(StateError::InvalidLayoutSlice {
453 start: layers.start,
454 end: layers.end,
455 layers: self.len(),
456 });
457 }
458 let policies = self
459 .layers
460 .iter()
461 .skip(layers.start)
462 .take(layers.len())
463 .cloned()
464 .collect::<Vec<_>>();
465 let mut segments = Vec::new();
466 for segment in &self.segments {
467 let start = segment.layers.start.max(layers.start);
468 let end = segment.layers.end.min(layers.end);
469 if start < end {
470 segments.push(StateSegmentSpec::new(
471 segment.id.as_str(),
472 start - layers.start..end - layers.start,
473 segment.lifetime,
474 segment.processed_token_offset,
475 )?);
476 }
477 }
478 Self::segmented(
479 LayerSchedule::new(policies.len(), policies)
480 .map_err(|error| StateError::InvalidResidency(error.to_string()))?,
481 segments,
482 )
483 }
484}
485
486pub trait RuntimeLayerState<B: NeuralBackend> {
488 type RetainedValues<'a>: Iterator<Item = &'a B::Tensor>
490 where
491 Self: 'a,
492 B::Tensor: 'a;
493
494 fn retained_values(&self) -> Self::RetainedValues<'_>;
496}
497
498pub trait ResettableRuntimeLayerState<B: NeuralBackend>: RuntimeLayerState<B> {
503 fn reset(&mut self) -> Result<(), StateError>;
505}
506
507pub trait RuntimeStateComponents<B: NeuralBackend>: RuntimeLayerState<B> {
513 fn position(&self) -> i32;
515
516 fn fixed_component(
518 &mut self,
519 role: StateTensorRole,
520 ) -> Result<&mut Option<B::Tensor>, StateError>;
521
522 fn advance_fixed(&mut self, tokens: i32) -> Result<(), StateError>;
524}
525
526pub trait RuntimeState<B: NeuralBackend> {
528 type RetainedValues<'a>: Iterator<Item = &'a B::Tensor>
530 where
531 Self: 'a,
532 B::Tensor: 'a;
533
534 fn layout(&self) -> &StateLayout;
536
537 fn optional_layout(&self) -> Option<&StateLayout> {
542 Some(self.layout())
543 }
544
545 fn retained_values(
550 &self,
551 ordinal: usize,
552 address: crate::ExecutionUnitAddress,
553 ) -> Result<Self::RetainedValues<'_>, StateError>;
554}
555
556pub trait ArchitectureStateFactory<B: NeuralBackend> {
562 type State: RuntimeState<B>;
564 type Error;
566
567 fn realize(&mut self, layout: &StateLayout) -> Result<Self::State, Self::Error>;
569}
570
571#[derive(Debug, thiserror::Error)]
573#[non_exhaustive]
574pub enum ArchitectureStateRealizationError<ArchitectureError, FactoryError> {
575 #[error("architecture state layout selection failed")]
577 Architecture(#[source] ArchitectureError),
578 #[error("backend state realization failed")]
580 Factory(#[source] FactoryError),
581 #[error("backend state realization changed the selected architecture layout")]
583 LayoutMismatch,
584}
585
586pub fn realize_architecture_state<B, M, F>(
589 architecture: &M,
590 factory: &mut F,
591) -> Result<F::State, ArchitectureStateRealizationError<M::DefinitionError, F::Error>>
592where
593 B: NeuralBackend,
594 M: crate::ArchitectureParameters<B>,
595 F: ArchitectureStateFactory<B>,
596{
597 let layout = architecture
598 .state_layout()
599 .map_err(ArchitectureStateRealizationError::Architecture)?;
600 let state = factory
601 .realize(&layout)
602 .map_err(ArchitectureStateRealizationError::Factory)?;
603 if state.layout() != &layout {
604 return Err(ArchitectureStateRealizationError::LayoutMismatch);
605 }
606 Ok(state)
607}
608
609pub trait ResettableRuntimeState<B: NeuralBackend>: RuntimeState<B> {
611 fn reset_segment(&mut self, segment: &StateSegmentId) -> Result<(), StateError>;
613}
614
615pub trait LayerRuntimeState<B: NeuralBackend>: RuntimeState<B> {
617 type LayerState: RuntimeLayerState<B>;
619
620 fn layer(&mut self, layer: usize) -> Result<&mut Self::LayerState, StateError>;
622}
623
624#[derive(Debug)]
626pub struct DeviceState<B: NeuralBackend, L> {
627 layout: Option<StateLayout>,
628 layers: Vec<L>,
629 backend: PhantomData<fn() -> B>,
630}
631
632impl<B: NeuralBackend, L: Clone> Clone for DeviceState<B, L> {
633 fn clone(&self) -> Self {
634 Self {
635 layout: self.layout.clone(),
636 layers: self.layers.clone(),
637 backend: PhantomData,
638 }
639 }
640
641 fn clone_from(&mut self, source: &Self) {
642 self.layout.clone_from(&source.layout);
643 self.layers.clone_from(&source.layers);
644 }
645}
646
647impl<B: NeuralBackend, L> DeviceState<B, L> {
648 pub fn create<E>(
650 layout: StateLayout,
651 mut create: impl FnMut(usize, &LayerCachePolicy) -> Result<L, E>,
652 ) -> Result<Self, E> {
653 let layers = layout
654 .layers()
655 .iter()
656 .enumerate()
657 .map(|(layer, policy)| create(layer, policy))
658 .collect::<Result<Vec<_>, _>>()?;
659 Ok(Self {
660 layout: Some(layout),
661 layers,
662 backend: PhantomData,
663 })
664 }
665
666 pub const fn stateless() -> Self {
668 Self {
669 layout: None,
670 layers: Vec::new(),
671 backend: PhantomData,
672 }
673 }
674}
675
676impl<B, L> RuntimeState<B> for DeviceState<B, L>
677where
678 B: NeuralBackend,
679 L: RuntimeLayerState<B>,
680{
681 type RetainedValues<'a>
682 = L::RetainedValues<'a>
683 where
684 Self: 'a,
685 B::Tensor: 'a;
686
687 fn layout(&self) -> &StateLayout {
688 self.layout
689 .as_ref()
690 .expect("layout requires a stateful DeviceState")
691 }
692
693 fn optional_layout(&self) -> Option<&StateLayout> {
694 self.layout.as_ref()
695 }
696
697 fn retained_values(
698 &self,
699 ordinal: usize,
700 _address: crate::ExecutionUnitAddress,
701 ) -> Result<Self::RetainedValues<'_>, StateError> {
702 let layer = ordinal;
703 self.layers
704 .get(layer)
705 .map(|layer| layer.retained_values())
706 .ok_or(StateError::UnknownLayer {
707 layer,
708 count: self.layers.len(),
709 })
710 }
711}
712
713impl<B, L> LayerRuntimeState<B> for DeviceState<B, L>
714where
715 B: NeuralBackend,
716 L: RuntimeLayerState<B>,
717{
718 type LayerState = L;
719
720 fn layer(&mut self, layer: usize) -> Result<&mut Self::LayerState, StateError> {
721 let count = self.layers.len();
722 self.layers
723 .get_mut(layer)
724 .ok_or(StateError::UnknownLayer { layer, count })
725 }
726}
727
728impl<B, L> ResettableRuntimeState<B> for DeviceState<B, L>
729where
730 B: NeuralBackend,
731 L: ResettableRuntimeLayerState<B>,
732{
733 fn reset_segment(&mut self, segment: &StateSegmentId) -> Result<(), StateError> {
734 let range = self
735 .layout
736 .as_ref()
737 .expect("stateful reset requires DeviceState layout")
738 .segment(segment)
739 .map(StateSegmentSpec::layers)
740 .ok_or_else(|| StateError::UnknownSegment {
741 segment: segment.clone(),
742 })?;
743 for layer in &mut self.layers[range] {
744 layer.reset()?;
745 }
746 Ok(())
747 }
748}
749
750impl<B: NeuralBackend, L> AsRef<[L]> for DeviceState<B, L> {
751 fn as_ref(&self) -> &[L] {
752 &self.layers
753 }
754}
755
756impl<B: NeuralBackend, L> AsMut<[L]> for DeviceState<B, L> {
757 fn as_mut(&mut self) -> &mut [L] {
758 &mut self.layers
759 }
760}
761
762#[derive(Debug, Clone, Eq, PartialEq)]
764pub struct ModelStateIdentity {
765 model_family: String,
767 effective_model_type: String,
769 architecture_fingerprint: String,
771 layer_count: usize,
773 global_layer_start: usize,
775 sink_tokens: usize,
777 topology: PromptCacheTopology,
779}
780
781impl ModelStateIdentity {
782 #[allow(clippy::too_many_arguments)]
784 pub fn new(
785 model_family: impl Into<String>,
786 effective_model_type: impl Into<String>,
787 architecture_fingerprint: impl Into<String>,
788 layer_count: usize,
789 global_layer_start: usize,
790 sink_tokens: usize,
791 topology: PromptCacheTopology,
792 ) -> Result<Self, PromptCacheError> {
793 let model_family = model_family.into();
794 let effective_model_type = effective_model_type.into();
795 let architecture_fingerprint = architecture_fingerprint.into();
796 if model_family.trim().is_empty()
797 || effective_model_type.trim().is_empty()
798 || architecture_fingerprint.trim().is_empty()
799 {
800 return Err(PromptCacheError::Malformed(
801 "model-state identity strings must be non-empty".into(),
802 ));
803 }
804 if layer_count == 0 || global_layer_start > layer_count {
805 return Err(PromptCacheError::Malformed(format!(
806 "model-state layer start {global_layer_start} is invalid for {layer_count} layers"
807 )));
808 }
809 topology.validate()?;
810 Ok(Self {
811 model_family,
812 effective_model_type,
813 architecture_fingerprint,
814 layer_count,
815 global_layer_start,
816 sink_tokens,
817 topology,
818 })
819 }
820
821 pub const fn layer_count(&self) -> usize {
823 self.layer_count
824 }
825
826 pub const fn global_layer_start(&self) -> usize {
828 self.global_layer_start
829 }
830
831 pub fn prompt_cache_identity(
833 &self,
834 layout: &StateLayout,
835 ) -> Result<PromptCacheModelIdentity, PromptCacheError> {
836 let global_layer_end = self
837 .global_layer_start
838 .checked_add(layout.len())
839 .ok_or_else(|| PromptCacheError::Malformed("owned layer range overflowed".into()))?;
840 PromptCacheModelIdentity::new(
841 self.model_family.clone(),
842 self.effective_model_type.clone(),
843 self.architecture_fingerprint.clone(),
844 self.layer_count,
845 self.global_layer_start,
846 global_layer_end,
847 self.sink_tokens,
848 self.topology.clone(),
849 layout.layers().clone(),
850 layout.layer_prefix_offsets(),
851 layout
852 .segments()
853 .iter()
854 .map(|segment| {
855 PromptCacheStateSegment::new(segment.id().as_str(), segment.layers())
856 })
857 .collect::<Result<Vec<_>, _>>()?,
858 )
859 }
860}
861
862#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
864#[non_exhaustive]
865pub enum StateError {
866 #[error("runtime state layout must contain at least one layer")]
868 EmptyLayout,
869 #[error("runtime state layout must contain at least one named segment")]
871 EmptySegments,
872 #[error("runtime state segment identity must not be empty")]
874 EmptySegmentId,
875 #[error("runtime state segment {segment:?} has empty layer range {start}..{end}")]
877 EmptySegmentRange {
878 segment: StateSegmentId,
880 start: usize,
882 end: usize,
884 },
885 #[error("runtime state segment {segment:?} has positive processed-token offset {offset}")]
887 PositiveSegmentOffset {
888 segment: StateSegmentId,
890 offset: i32,
892 },
893 #[error("runtime state layout cannot select range {start}..{end} from {layers} layers")]
895 InvalidLayoutSlice {
896 start: usize,
898 end: usize,
900 layers: usize,
902 },
903 #[error("runtime state segment {segment:?} is declared more than once")]
905 DuplicateSegment {
906 segment: StateSegmentId,
908 },
909 #[error(
911 "runtime state segment {segment:?} range {start}..{end} exceeds {layers} layout layers"
912 )]
913 SegmentOutOfBounds {
914 segment: StateSegmentId,
916 start: usize,
918 end: usize,
920 layers: usize,
922 },
923 #[error(
925 "runtime state segment {segment:?} starts at layer {start}, before prior frontier {frontier}"
926 )]
927 OverlappingSegment {
928 segment: StateSegmentId,
930 start: usize,
932 frontier: usize,
934 },
935 #[error("runtime state layer {layer} is not assigned to a named segment")]
937 UnassignedStateLayer {
938 layer: usize,
940 },
941 #[error("runtime state layout has no segment {segment:?}")]
943 UnknownSegment {
944 segment: StateSegmentId,
946 },
947 #[error("runtime state reset failed: {0}")]
949 ResetFailed(String),
950 #[error("invalid runtime state policy for layer {layer}: {reason}")]
952 InvalidLayer {
953 layer: usize,
955 reason: String,
957 },
958 #[error("invalid runtime state residency plan: {0}")]
960 InvalidResidency(String),
961 #[error("runtime state layer {layer} is outside the {count}-layer layout")]
963 UnknownLayer {
964 layer: usize,
966 count: usize,
968 },
969 #[error("runtime state layer does not declare fixed component {role:?}")]
971 UnknownComponent {
972 role: StateTensorRole,
974 },
975 #[error("invalid fixed-state advance: {0}")]
977 InvalidAdvance(String),
978}
979
980#[cfg(test)]
981mod tests {
982 use super::*;
983 use eredu_core::{AttentionPolicy, LayerSchedule};
984
985 fn layout() -> StateLayout {
986 StateLayout::new(
987 LayerSchedule::new(
988 2,
989 vec![
990 LayerCachePolicy::key_value(AttentionPolicy::Full, 2, 8).unwrap(),
991 LayerCachePolicy::key_value(
992 AttentionPolicy::from_sliding_window(Some(16)).unwrap(),
993 2,
994 8,
995 )
996 .unwrap(),
997 ],
998 )
999 .unwrap(),
1000 )
1001 .unwrap()
1002 }
1003
1004 #[test]
1005 fn prompt_identity_is_derived_from_layout_and_placement() {
1006 let layout = layout();
1007 let identity = ModelStateIdentity {
1008 model_family: "fixture".into(),
1009 effective_model_type: "fixture-v1".into(),
1010 architecture_fingerprint: "geometry-1".into(),
1011 layer_count: 4,
1012 global_layer_start: 1,
1013 sink_tokens: 0,
1014 topology: PromptCacheTopology::default(),
1015 }
1016 .prompt_cache_identity(&layout)
1017 .unwrap();
1018 assert_eq!(identity.global_layer_start(), 1);
1019 assert_eq!(identity.global_layer_end(), 3);
1020 assert_eq!(identity.layer_layout(), layout.layers());
1021 }
1022
1023 #[test]
1024 fn prompt_identity_derives_offsets_from_segments() {
1025 let policy = LayerCachePolicy::key_value(AttentionPolicy::Full, 1, 8).unwrap();
1026 let layout = StateLayout::segmented(
1027 LayerSchedule::new(2, vec![policy.clone(), policy]).unwrap(),
1028 [
1029 StateSegmentSpec::new("target", 0..1, StateSegmentLifetime::Persistent, 0).unwrap(),
1030 StateSegmentSpec::new("prediction", 1..2, StateSegmentLifetime::Persistent, -1)
1031 .unwrap(),
1032 ],
1033 )
1034 .unwrap();
1035 let identity = ModelStateIdentity {
1036 model_family: "fixture".into(),
1037 effective_model_type: "fixture-v1".into(),
1038 architecture_fingerprint: "geometry-1".into(),
1039 layer_count: 2,
1040 global_layer_start: 0,
1041 sink_tokens: 0,
1042 topology: PromptCacheTopology::default(),
1043 }
1044 .prompt_cache_identity(&layout)
1045 .unwrap();
1046 assert_eq!(identity.layer_prefix_offsets(), [0, -1]);
1047 assert_eq!(identity.state_segments().len(), 2);
1048 assert_eq!(identity.state_segments()[0].id(), "target");
1049 assert_eq!(identity.state_segments()[0].layers(), 0..1);
1050 assert_eq!(identity.state_segments()[1].id(), "prediction");
1051 assert_eq!(identity.state_segments()[1].layers(), 1..2);
1052
1053 let prediction = identity.select_state_segment("prediction").unwrap();
1054 assert_eq!(prediction.global_layer_start(), 1);
1055 assert_eq!(prediction.global_layer_end(), 2);
1056 assert_eq!(prediction.layer_prefix_offsets(), [-1]);
1057 assert_eq!(prediction.state_segments()[0].id(), "prediction");
1058 assert_eq!(prediction.state_segments()[0].layers(), 0..1);
1059 }
1060
1061 #[test]
1062 fn state_layout_exposes_stable_semantic_component_names() {
1063 let layout = StateLayout::new(
1064 LayerSchedule::new(
1065 1,
1066 vec![
1067 LayerCachePolicy::compressed_latent_rotary(AttentionPolicy::Full, 16, 8)
1068 .unwrap(),
1069 ],
1070 )
1071 .unwrap(),
1072 )
1073 .unwrap();
1074 let names = layout
1075 .components(0)
1076 .unwrap()
1077 .iter()
1078 .map(|component| component.role().stable_name())
1079 .collect::<Vec<_>>();
1080 assert_eq!(
1081 names,
1082 ["attention.compressed_latent", "attention.rotary_keys"]
1083 );
1084 }
1085
1086 fn four_layer_schedule() -> LayerSchedule<LayerCachePolicy> {
1087 LayerSchedule::new(
1088 4,
1089 (0..4)
1090 .map(|_| LayerCachePolicy::key_value(AttentionPolicy::Full, 1, 8).unwrap())
1091 .collect(),
1092 )
1093 .unwrap()
1094 }
1095
1096 #[test]
1097 fn composite_state_segments_are_canonical_and_cover_every_layer() {
1098 let layout = StateLayout::segmented(
1099 four_layer_schedule(),
1100 [
1101 StateSegmentSpec::new("depth", 2..4, StateSegmentLifetime::FrameLocal, 0).unwrap(),
1102 StateSegmentSpec::new("temporal", 0..2, StateSegmentLifetime::Persistent, 0)
1103 .unwrap(),
1104 ],
1105 )
1106 .unwrap();
1107
1108 assert_eq!(
1109 layout
1110 .segments()
1111 .iter()
1112 .map(|segment| (segment.id().as_str(), segment.layers(), segment.lifetime()))
1113 .collect::<Vec<_>>(),
1114 [
1115 ("temporal", 0..2, StateSegmentLifetime::Persistent),
1116 ("depth", 2..4, StateSegmentLifetime::FrameLocal),
1117 ]
1118 );
1119 assert_eq!(
1120 layout.segment_for_layer(0).unwrap().id().as_str(),
1121 "temporal"
1122 );
1123 assert_eq!(layout.segment_for_layer(3).unwrap().id().as_str(), "depth");
1124 assert!(layout.segment_for_layer(4).is_none());
1125 }
1126
1127 #[test]
1128 fn state_layout_slice_preserves_and_rebases_segment_frontiers() {
1129 let layout = StateLayout::segmented(
1130 four_layer_schedule(),
1131 [
1132 StateSegmentSpec::new("target", 0..2, StateSegmentLifetime::Persistent, 0).unwrap(),
1133 StateSegmentSpec::new("prediction", 2..4, StateSegmentLifetime::Persistent, -1)
1134 .unwrap(),
1135 ],
1136 )
1137 .unwrap();
1138
1139 let sliced = layout.slice(1..4).unwrap();
1140 assert_eq!(sliced.segments()[0].layers(), 0..1);
1141 assert_eq!(sliced.segments()[1].layers(), 1..3);
1142 assert_eq!(sliced.layer_prefix_offsets(), [0, -1, -1]);
1143 }
1144
1145 #[test]
1146 fn segment_identity_lifetime_and_offset_participate_in_layout_equality() {
1147 let layout = |depth_name, lifetime, offset| {
1148 StateLayout::segmented(
1149 four_layer_schedule(),
1150 [
1151 StateSegmentSpec::new("temporal", 0..2, StateSegmentLifetime::Persistent, 0)
1152 .unwrap(),
1153 StateSegmentSpec::new(depth_name, 2..4, lifetime, offset).unwrap(),
1154 ],
1155 )
1156 .unwrap()
1157 };
1158 let canonical = layout("depth", StateSegmentLifetime::FrameLocal, 0);
1159 assert_ne!(
1160 canonical,
1161 layout("predictor", StateSegmentLifetime::FrameLocal, 0)
1162 );
1163 assert_ne!(
1164 canonical,
1165 layout("depth", StateSegmentLifetime::Persistent, 0)
1166 );
1167 assert_ne!(
1168 canonical,
1169 layout("depth", StateSegmentLifetime::FrameLocal, -1)
1170 );
1171 }
1172
1173 #[test]
1174 fn malformed_segment_partitions_fail_closed() {
1175 let duplicate = StateLayout::segmented(
1176 four_layer_schedule(),
1177 [
1178 StateSegmentSpec::new("cache", 0..2, StateSegmentLifetime::Persistent, 0).unwrap(),
1179 StateSegmentSpec::new("cache", 2..4, StateSegmentLifetime::FrameLocal, 0).unwrap(),
1180 ],
1181 )
1182 .unwrap_err();
1183 assert!(matches!(duplicate, StateError::DuplicateSegment { .. }));
1184
1185 let overlap = StateLayout::segmented(
1186 four_layer_schedule(),
1187 [
1188 StateSegmentSpec::new("left", 0..3, StateSegmentLifetime::Persistent, 0).unwrap(),
1189 StateSegmentSpec::new("right", 2..4, StateSegmentLifetime::FrameLocal, 0).unwrap(),
1190 ],
1191 )
1192 .unwrap_err();
1193 assert!(matches!(overlap, StateError::OverlappingSegment { .. }));
1194
1195 let gap = StateLayout::segmented(
1196 four_layer_schedule(),
1197 [
1198 StateSegmentSpec::new("left", 0..1, StateSegmentLifetime::Persistent, 0).unwrap(),
1199 StateSegmentSpec::new("right", 2..4, StateSegmentLifetime::FrameLocal, 0).unwrap(),
1200 ],
1201 )
1202 .unwrap_err();
1203 assert_eq!(gap, StateError::UnassignedStateLayer { layer: 1 });
1204
1205 let outside = StateLayout::segmented(
1206 four_layer_schedule(),
1207 [StateSegmentSpec::new("all", 0..5, StateSegmentLifetime::Persistent, 0).unwrap()],
1208 )
1209 .unwrap_err();
1210 assert!(matches!(outside, StateError::SegmentOutOfBounds { .. }));
1211 }
1212}