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 is_empty(&self) -> bool {
370 self.layers.is_empty()
371 }
372
373 pub fn layer(&self, layer: usize) -> Option<&LayerCachePolicy> {
375 self.layers.get(layer)
376 }
377
378 pub const fn layers(&self) -> &LayerSchedule<LayerCachePolicy> {
380 &self.layers
381 }
382
383 pub fn components(&self, layer: usize) -> Option<&[StateComponentPolicy]> {
385 self.components.get(layer).map(Vec::as_slice)
386 }
387
388 pub fn segments(&self) -> &[StateSegmentSpec] {
390 &self.segments
391 }
392
393 pub fn segment(&self, id: &StateSegmentId) -> Option<&StateSegmentSpec> {
395 self.segments.iter().find(|segment| segment.id() == id)
396 }
397
398 pub fn segment_for_layer(&self, layer: usize) -> Option<&StateSegmentSpec> {
400 self.segments
401 .iter()
402 .find(|segment| segment.layers.contains(&layer))
403 }
404
405 pub fn layer_prefix_offsets(&self) -> Vec<i32> {
407 let mut offsets = Vec::with_capacity(self.len());
408 for segment in &self.segments {
409 offsets.extend(std::iter::repeat_n(
410 segment.processed_token_offset(),
411 segment.layers.len(),
412 ));
413 }
414 offsets
415 }
416
417 pub fn slice(&self, layers: Range<usize>) -> Result<Self, StateError> {
420 if layers.is_empty() || layers.end > self.len() {
421 return Err(StateError::InvalidLayoutSlice {
422 start: layers.start,
423 end: layers.end,
424 layers: self.len(),
425 });
426 }
427 let policies = self
428 .layers
429 .iter()
430 .skip(layers.start)
431 .take(layers.len())
432 .cloned()
433 .collect::<Vec<_>>();
434 let mut segments = Vec::new();
435 for segment in &self.segments {
436 let start = segment.layers.start.max(layers.start);
437 let end = segment.layers.end.min(layers.end);
438 if start < end {
439 segments.push(StateSegmentSpec::new(
440 segment.id.as_str(),
441 start - layers.start..end - layers.start,
442 segment.lifetime,
443 segment.processed_token_offset,
444 )?);
445 }
446 }
447 Self::segmented(
448 LayerSchedule::new(policies.len(), policies)
449 .map_err(|error| StateError::InvalidResidency(error.to_string()))?,
450 segments,
451 )
452 }
453}
454
455pub trait RuntimeLayerState<B: NeuralBackend> {
457 type RetainedValues<'a>: Iterator<Item = &'a B::Tensor>
459 where
460 Self: 'a,
461 B::Tensor: 'a;
462
463 fn retained_values(&self) -> Self::RetainedValues<'_>;
465}
466
467pub trait ResettableRuntimeLayerState<B: NeuralBackend>: RuntimeLayerState<B> {
472 fn reset(&mut self) -> Result<(), StateError>;
474}
475
476pub trait RuntimeStateComponents<B: NeuralBackend>: RuntimeLayerState<B> {
482 fn position(&self) -> i32;
484
485 fn fixed_component(
487 &mut self,
488 role: StateTensorRole,
489 ) -> Result<&mut Option<B::Tensor>, StateError>;
490
491 fn advance_fixed(&mut self, tokens: i32) -> Result<(), StateError>;
493}
494
495pub trait RuntimeState<B: NeuralBackend> {
497 type RetainedValues<'a>: Iterator<Item = &'a B::Tensor>
499 where
500 Self: 'a,
501 B::Tensor: 'a;
502
503 fn layout(&self) -> &StateLayout;
505
506 fn optional_layout(&self) -> Option<&StateLayout> {
511 Some(self.layout())
512 }
513
514 fn retained_values(
519 &self,
520 ordinal: usize,
521 address: crate::ExecutionUnitAddress,
522 ) -> Result<Self::RetainedValues<'_>, StateError>;
523}
524
525pub trait ArchitectureStateFactory<B: NeuralBackend> {
531 type State: RuntimeState<B>;
533 type Error;
535
536 fn realize(&mut self, layout: &StateLayout) -> Result<Self::State, Self::Error>;
538}
539
540#[derive(Debug, thiserror::Error)]
542#[non_exhaustive]
543pub enum ArchitectureStateRealizationError<ArchitectureError, FactoryError> {
544 #[error("architecture state layout selection failed")]
546 Architecture(#[source] ArchitectureError),
547 #[error("backend state realization failed")]
549 Factory(#[source] FactoryError),
550 #[error("backend state realization changed the selected architecture layout")]
552 LayoutMismatch,
553}
554
555pub fn realize_architecture_state<B, M, F>(
558 architecture: &M,
559 factory: &mut F,
560) -> Result<F::State, ArchitectureStateRealizationError<M::DefinitionError, F::Error>>
561where
562 B: NeuralBackend,
563 M: crate::ArchitectureParameters<B>,
564 F: ArchitectureStateFactory<B>,
565{
566 let layout = architecture
567 .state_layout()
568 .map_err(ArchitectureStateRealizationError::Architecture)?;
569 let state = factory
570 .realize(&layout)
571 .map_err(ArchitectureStateRealizationError::Factory)?;
572 if state.layout() != &layout {
573 return Err(ArchitectureStateRealizationError::LayoutMismatch);
574 }
575 Ok(state)
576}
577
578pub trait ResettableRuntimeState<B: NeuralBackend>: RuntimeState<B> {
580 fn reset_segment(&mut self, segment: &StateSegmentId) -> Result<(), StateError>;
582}
583
584pub trait LayerRuntimeState<B: NeuralBackend>: RuntimeState<B> {
586 type LayerState: RuntimeLayerState<B>;
588
589 fn layer(&mut self, layer: usize) -> Result<&mut Self::LayerState, StateError>;
591}
592
593#[derive(Debug)]
595pub struct DeviceState<B: NeuralBackend, L> {
596 layout: Option<StateLayout>,
597 layers: Vec<L>,
598 backend: PhantomData<fn() -> B>,
599}
600
601impl<B: NeuralBackend, L: Clone> Clone for DeviceState<B, L> {
602 fn clone(&self) -> Self {
603 Self {
604 layout: self.layout.clone(),
605 layers: self.layers.clone(),
606 backend: PhantomData,
607 }
608 }
609
610 fn clone_from(&mut self, source: &Self) {
611 self.layout.clone_from(&source.layout);
612 self.layers.clone_from(&source.layers);
613 }
614}
615
616impl<B: NeuralBackend, L> DeviceState<B, L> {
617 pub fn create<E>(
619 layout: StateLayout,
620 mut create: impl FnMut(usize, &LayerCachePolicy) -> Result<L, E>,
621 ) -> Result<Self, E> {
622 let layers = layout
623 .layers()
624 .iter()
625 .enumerate()
626 .map(|(layer, policy)| create(layer, policy))
627 .collect::<Result<Vec<_>, _>>()?;
628 Ok(Self {
629 layout: Some(layout),
630 layers,
631 backend: PhantomData,
632 })
633 }
634
635 pub const fn stateless() -> Self {
637 Self {
638 layout: None,
639 layers: Vec::new(),
640 backend: PhantomData,
641 }
642 }
643}
644
645impl<B, L> RuntimeState<B> for DeviceState<B, L>
646where
647 B: NeuralBackend,
648 L: RuntimeLayerState<B>,
649{
650 type RetainedValues<'a>
651 = L::RetainedValues<'a>
652 where
653 Self: 'a,
654 B::Tensor: 'a;
655
656 fn layout(&self) -> &StateLayout {
657 self.layout
658 .as_ref()
659 .expect("layout requires a stateful DeviceState")
660 }
661
662 fn optional_layout(&self) -> Option<&StateLayout> {
663 self.layout.as_ref()
664 }
665
666 fn retained_values(
667 &self,
668 ordinal: usize,
669 _address: crate::ExecutionUnitAddress,
670 ) -> Result<Self::RetainedValues<'_>, StateError> {
671 let layer = ordinal;
672 self.layers
673 .get(layer)
674 .map(|layer| layer.retained_values())
675 .ok_or(StateError::UnknownLayer {
676 layer,
677 count: self.layers.len(),
678 })
679 }
680}
681
682impl<B, L> LayerRuntimeState<B> for DeviceState<B, L>
683where
684 B: NeuralBackend,
685 L: RuntimeLayerState<B>,
686{
687 type LayerState = L;
688
689 fn layer(&mut self, layer: usize) -> Result<&mut Self::LayerState, StateError> {
690 let count = self.layers.len();
691 self.layers
692 .get_mut(layer)
693 .ok_or(StateError::UnknownLayer { layer, count })
694 }
695}
696
697impl<B, L> ResettableRuntimeState<B> for DeviceState<B, L>
698where
699 B: NeuralBackend,
700 L: ResettableRuntimeLayerState<B>,
701{
702 fn reset_segment(&mut self, segment: &StateSegmentId) -> Result<(), StateError> {
703 let range = self
704 .layout
705 .as_ref()
706 .expect("stateful reset requires DeviceState layout")
707 .segment(segment)
708 .map(StateSegmentSpec::layers)
709 .ok_or_else(|| StateError::UnknownSegment {
710 segment: segment.clone(),
711 })?;
712 for layer in &mut self.layers[range] {
713 layer.reset()?;
714 }
715 Ok(())
716 }
717}
718
719impl<B: NeuralBackend, L> AsRef<[L]> for DeviceState<B, L> {
720 fn as_ref(&self) -> &[L] {
721 &self.layers
722 }
723}
724
725impl<B: NeuralBackend, L> AsMut<[L]> for DeviceState<B, L> {
726 fn as_mut(&mut self) -> &mut [L] {
727 &mut self.layers
728 }
729}
730
731#[derive(Debug, Clone, Eq, PartialEq)]
733pub struct ModelStateIdentity {
734 model_family: String,
736 effective_model_type: String,
738 architecture_fingerprint: String,
740 layer_count: usize,
742 global_layer_start: usize,
744 sink_tokens: usize,
746 topology: PromptCacheTopology,
748}
749
750impl ModelStateIdentity {
751 #[allow(clippy::too_many_arguments)]
753 pub fn new(
754 model_family: impl Into<String>,
755 effective_model_type: impl Into<String>,
756 architecture_fingerprint: impl Into<String>,
757 layer_count: usize,
758 global_layer_start: usize,
759 sink_tokens: usize,
760 topology: PromptCacheTopology,
761 ) -> Result<Self, PromptCacheError> {
762 let model_family = model_family.into();
763 let effective_model_type = effective_model_type.into();
764 let architecture_fingerprint = architecture_fingerprint.into();
765 if model_family.trim().is_empty()
766 || effective_model_type.trim().is_empty()
767 || architecture_fingerprint.trim().is_empty()
768 {
769 return Err(PromptCacheError::Malformed(
770 "model-state identity strings must be non-empty".into(),
771 ));
772 }
773 if layer_count == 0 || global_layer_start > layer_count {
774 return Err(PromptCacheError::Malformed(format!(
775 "model-state layer start {global_layer_start} is invalid for {layer_count} layers"
776 )));
777 }
778 topology.validate()?;
779 Ok(Self {
780 model_family,
781 effective_model_type,
782 architecture_fingerprint,
783 layer_count,
784 global_layer_start,
785 sink_tokens,
786 topology,
787 })
788 }
789
790 pub const fn layer_count(&self) -> usize {
792 self.layer_count
793 }
794
795 pub const fn global_layer_start(&self) -> usize {
797 self.global_layer_start
798 }
799
800 pub fn prompt_cache_identity(
802 &self,
803 layout: &StateLayout,
804 ) -> Result<PromptCacheModelIdentity, PromptCacheError> {
805 let global_layer_end = self
806 .global_layer_start
807 .checked_add(layout.len())
808 .ok_or_else(|| PromptCacheError::Malformed("owned layer range overflowed".into()))?;
809 PromptCacheModelIdentity::new(
810 self.model_family.clone(),
811 self.effective_model_type.clone(),
812 self.architecture_fingerprint.clone(),
813 self.layer_count,
814 self.global_layer_start,
815 global_layer_end,
816 self.sink_tokens,
817 self.topology.clone(),
818 layout.layers().clone(),
819 layout.layer_prefix_offsets(),
820 layout
821 .segments()
822 .iter()
823 .map(|segment| {
824 PromptCacheStateSegment::new(segment.id().as_str(), segment.layers())
825 })
826 .collect::<Result<Vec<_>, _>>()?,
827 )
828 }
829}
830
831#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
833#[non_exhaustive]
834pub enum StateError {
835 #[error("runtime state layout must contain at least one layer")]
837 EmptyLayout,
838 #[error("runtime state layout must contain at least one named segment")]
840 EmptySegments,
841 #[error("runtime state segment identity must not be empty")]
843 EmptySegmentId,
844 #[error("runtime state segment {segment:?} has empty layer range {start}..{end}")]
846 EmptySegmentRange {
847 segment: StateSegmentId,
849 start: usize,
851 end: usize,
853 },
854 #[error("runtime state segment {segment:?} has positive processed-token offset {offset}")]
856 PositiveSegmentOffset {
857 segment: StateSegmentId,
859 offset: i32,
861 },
862 #[error("runtime state layout cannot select range {start}..{end} from {layers} layers")]
864 InvalidLayoutSlice {
865 start: usize,
867 end: usize,
869 layers: usize,
871 },
872 #[error("runtime state segment {segment:?} is declared more than once")]
874 DuplicateSegment {
875 segment: StateSegmentId,
877 },
878 #[error(
880 "runtime state segment {segment:?} range {start}..{end} exceeds {layers} layout layers"
881 )]
882 SegmentOutOfBounds {
883 segment: StateSegmentId,
885 start: usize,
887 end: usize,
889 layers: usize,
891 },
892 #[error(
894 "runtime state segment {segment:?} starts at layer {start}, before prior frontier {frontier}"
895 )]
896 OverlappingSegment {
897 segment: StateSegmentId,
899 start: usize,
901 frontier: usize,
903 },
904 #[error("runtime state layer {layer} is not assigned to a named segment")]
906 UnassignedStateLayer {
907 layer: usize,
909 },
910 #[error("runtime state layout has no segment {segment:?}")]
912 UnknownSegment {
913 segment: StateSegmentId,
915 },
916 #[error("runtime state reset failed: {0}")]
918 ResetFailed(String),
919 #[error("invalid runtime state policy for layer {layer}: {reason}")]
921 InvalidLayer {
922 layer: usize,
924 reason: String,
926 },
927 #[error("invalid runtime state residency plan: {0}")]
929 InvalidResidency(String),
930 #[error("runtime state layer {layer} is outside the {count}-layer layout")]
932 UnknownLayer {
933 layer: usize,
935 count: usize,
937 },
938 #[error("runtime state layer does not declare fixed component {role:?}")]
940 UnknownComponent {
941 role: StateTensorRole,
943 },
944 #[error("invalid fixed-state advance: {0}")]
946 InvalidAdvance(String),
947}
948
949#[cfg(test)]
950mod tests {
951 use super::*;
952 use eredu_core::{AttentionPolicy, LayerSchedule};
953
954 fn layout() -> StateLayout {
955 StateLayout::new(
956 LayerSchedule::new(
957 2,
958 vec![
959 LayerCachePolicy::key_value(AttentionPolicy::Full, 2, 8).unwrap(),
960 LayerCachePolicy::key_value(
961 AttentionPolicy::from_sliding_window(Some(16)).unwrap(),
962 2,
963 8,
964 )
965 .unwrap(),
966 ],
967 )
968 .unwrap(),
969 )
970 .unwrap()
971 }
972
973 #[test]
974 fn prompt_identity_is_derived_from_layout_and_placement() {
975 let layout = layout();
976 let identity = ModelStateIdentity {
977 model_family: "fixture".into(),
978 effective_model_type: "fixture-v1".into(),
979 architecture_fingerprint: "geometry-1".into(),
980 layer_count: 4,
981 global_layer_start: 1,
982 sink_tokens: 0,
983 topology: PromptCacheTopology::default(),
984 }
985 .prompt_cache_identity(&layout)
986 .unwrap();
987 assert_eq!(identity.global_layer_start(), 1);
988 assert_eq!(identity.global_layer_end(), 3);
989 assert_eq!(identity.layer_layout(), layout.layers());
990 }
991
992 #[test]
993 fn prompt_identity_derives_offsets_from_segments() {
994 let policy = LayerCachePolicy::key_value(AttentionPolicy::Full, 1, 8).unwrap();
995 let layout = StateLayout::segmented(
996 LayerSchedule::new(2, vec![policy.clone(), policy]).unwrap(),
997 [
998 StateSegmentSpec::new("target", 0..1, StateSegmentLifetime::Persistent, 0).unwrap(),
999 StateSegmentSpec::new("prediction", 1..2, StateSegmentLifetime::Persistent, -1)
1000 .unwrap(),
1001 ],
1002 )
1003 .unwrap();
1004 let identity = ModelStateIdentity {
1005 model_family: "fixture".into(),
1006 effective_model_type: "fixture-v1".into(),
1007 architecture_fingerprint: "geometry-1".into(),
1008 layer_count: 2,
1009 global_layer_start: 0,
1010 sink_tokens: 0,
1011 topology: PromptCacheTopology::default(),
1012 }
1013 .prompt_cache_identity(&layout)
1014 .unwrap();
1015 assert_eq!(identity.layer_prefix_offsets(), [0, -1]);
1016 assert_eq!(identity.state_segments().len(), 2);
1017 assert_eq!(identity.state_segments()[0].id(), "target");
1018 assert_eq!(identity.state_segments()[0].layers(), 0..1);
1019 assert_eq!(identity.state_segments()[1].id(), "prediction");
1020 assert_eq!(identity.state_segments()[1].layers(), 1..2);
1021
1022 let prediction = identity.select_state_segment("prediction").unwrap();
1023 assert_eq!(prediction.global_layer_start(), 1);
1024 assert_eq!(prediction.global_layer_end(), 2);
1025 assert_eq!(prediction.layer_prefix_offsets(), [-1]);
1026 assert_eq!(prediction.state_segments()[0].id(), "prediction");
1027 assert_eq!(prediction.state_segments()[0].layers(), 0..1);
1028 }
1029
1030 #[test]
1031 fn state_layout_exposes_stable_semantic_component_names() {
1032 let layout = StateLayout::new(
1033 LayerSchedule::new(
1034 1,
1035 vec![
1036 LayerCachePolicy::compressed_latent_rotary(AttentionPolicy::Full, 16, 8)
1037 .unwrap(),
1038 ],
1039 )
1040 .unwrap(),
1041 )
1042 .unwrap();
1043 let names = layout
1044 .components(0)
1045 .unwrap()
1046 .iter()
1047 .map(|component| component.role().stable_name())
1048 .collect::<Vec<_>>();
1049 assert_eq!(
1050 names,
1051 ["attention.compressed_latent", "attention.rotary_keys"]
1052 );
1053 }
1054
1055 fn four_layer_schedule() -> LayerSchedule<LayerCachePolicy> {
1056 LayerSchedule::new(
1057 4,
1058 (0..4)
1059 .map(|_| LayerCachePolicy::key_value(AttentionPolicy::Full, 1, 8).unwrap())
1060 .collect(),
1061 )
1062 .unwrap()
1063 }
1064
1065 #[test]
1066 fn composite_state_segments_are_canonical_and_cover_every_layer() {
1067 let layout = StateLayout::segmented(
1068 four_layer_schedule(),
1069 [
1070 StateSegmentSpec::new("depth", 2..4, StateSegmentLifetime::FrameLocal, 0).unwrap(),
1071 StateSegmentSpec::new("temporal", 0..2, StateSegmentLifetime::Persistent, 0)
1072 .unwrap(),
1073 ],
1074 )
1075 .unwrap();
1076
1077 assert_eq!(
1078 layout
1079 .segments()
1080 .iter()
1081 .map(|segment| (segment.id().as_str(), segment.layers(), segment.lifetime()))
1082 .collect::<Vec<_>>(),
1083 [
1084 ("temporal", 0..2, StateSegmentLifetime::Persistent),
1085 ("depth", 2..4, StateSegmentLifetime::FrameLocal),
1086 ]
1087 );
1088 assert_eq!(
1089 layout.segment_for_layer(0).unwrap().id().as_str(),
1090 "temporal"
1091 );
1092 assert_eq!(layout.segment_for_layer(3).unwrap().id().as_str(), "depth");
1093 assert!(layout.segment_for_layer(4).is_none());
1094 }
1095
1096 #[test]
1097 fn state_layout_slice_preserves_and_rebases_segment_frontiers() {
1098 let layout = StateLayout::segmented(
1099 four_layer_schedule(),
1100 [
1101 StateSegmentSpec::new("target", 0..2, StateSegmentLifetime::Persistent, 0).unwrap(),
1102 StateSegmentSpec::new("prediction", 2..4, StateSegmentLifetime::Persistent, -1)
1103 .unwrap(),
1104 ],
1105 )
1106 .unwrap();
1107
1108 let sliced = layout.slice(1..4).unwrap();
1109 assert_eq!(sliced.segments()[0].layers(), 0..1);
1110 assert_eq!(sliced.segments()[1].layers(), 1..3);
1111 assert_eq!(sliced.layer_prefix_offsets(), [0, -1, -1]);
1112 }
1113
1114 #[test]
1115 fn segment_identity_lifetime_and_offset_participate_in_layout_equality() {
1116 let layout = |depth_name, lifetime, offset| {
1117 StateLayout::segmented(
1118 four_layer_schedule(),
1119 [
1120 StateSegmentSpec::new("temporal", 0..2, StateSegmentLifetime::Persistent, 0)
1121 .unwrap(),
1122 StateSegmentSpec::new(depth_name, 2..4, lifetime, offset).unwrap(),
1123 ],
1124 )
1125 .unwrap()
1126 };
1127 let canonical = layout("depth", StateSegmentLifetime::FrameLocal, 0);
1128 assert_ne!(
1129 canonical,
1130 layout("predictor", StateSegmentLifetime::FrameLocal, 0)
1131 );
1132 assert_ne!(
1133 canonical,
1134 layout("depth", StateSegmentLifetime::Persistent, 0)
1135 );
1136 assert_ne!(
1137 canonical,
1138 layout("depth", StateSegmentLifetime::FrameLocal, -1)
1139 );
1140 }
1141
1142 #[test]
1143 fn malformed_segment_partitions_fail_closed() {
1144 let duplicate = StateLayout::segmented(
1145 four_layer_schedule(),
1146 [
1147 StateSegmentSpec::new("cache", 0..2, StateSegmentLifetime::Persistent, 0).unwrap(),
1148 StateSegmentSpec::new("cache", 2..4, StateSegmentLifetime::FrameLocal, 0).unwrap(),
1149 ],
1150 )
1151 .unwrap_err();
1152 assert!(matches!(duplicate, StateError::DuplicateSegment { .. }));
1153
1154 let overlap = StateLayout::segmented(
1155 four_layer_schedule(),
1156 [
1157 StateSegmentSpec::new("left", 0..3, StateSegmentLifetime::Persistent, 0).unwrap(),
1158 StateSegmentSpec::new("right", 2..4, StateSegmentLifetime::FrameLocal, 0).unwrap(),
1159 ],
1160 )
1161 .unwrap_err();
1162 assert!(matches!(overlap, StateError::OverlappingSegment { .. }));
1163
1164 let gap = StateLayout::segmented(
1165 four_layer_schedule(),
1166 [
1167 StateSegmentSpec::new("left", 0..1, StateSegmentLifetime::Persistent, 0).unwrap(),
1168 StateSegmentSpec::new("right", 2..4, StateSegmentLifetime::FrameLocal, 0).unwrap(),
1169 ],
1170 )
1171 .unwrap_err();
1172 assert_eq!(gap, StateError::UnassignedStateLayer { layer: 1 });
1173
1174 let outside = StateLayout::segmented(
1175 four_layer_schedule(),
1176 [StateSegmentSpec::new("all", 0..5, StateSegmentLifetime::Persistent, 0).unwrap()],
1177 )
1178 .unwrap_err();
1179 assert!(matches!(outside, StateError::SegmentOutOfBounds { .. }));
1180 }
1181}