Skip to main content

eredu_runtime/
state.rs

1//! Backend-neutral mutable model-state contracts.
2//!
3//! Architectures declare state geometry through [`StateLayout`]. Runtime
4//! policies select a residency realization, while concrete backends retain
5//! their native layer-state and tensor types.
6
7use 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/// Architecture-declared placement of one contiguous mutable-state range.
19#[derive(Debug, Clone, Copy, Eq, PartialEq)]
20#[non_exhaustive]
21pub enum ArchitectureStatePlacement {
22    /// Partition the state range in lockstep with one execution group's units.
23    GroupUnits {
24        /// Canonical execution-group slot.
25        group: usize,
26    },
27    /// Attach the complete state range to the realized architecture output owner.
28    OutputOwner,
29}
30
31/// One architecture-authored rule in a mutable-state partition plan.
32#[derive(Debug, Clone, Eq, PartialEq)]
33pub struct ArchitectureStatePartitionRule {
34    layers: Range<usize>,
35    placement: ArchitectureStatePlacement,
36}
37
38impl ArchitectureStatePartitionRule {
39    /// Aligns a state range one-for-one with an execution group's unit indices.
40    pub fn group_units(group: usize, layers: Range<usize>) -> Self {
41        Self {
42            layers,
43            placement: ArchitectureStatePlacement::GroupUnits { group },
44        }
45    }
46
47    /// Attaches a complete state range to the architecture output owner.
48    pub fn output_owner(layers: Range<usize>) -> Self {
49        Self {
50            layers,
51            placement: ArchitectureStatePlacement::OutputOwner,
52        }
53    }
54
55    /// Returns the architecture-global state-layer range governed by this rule.
56    pub fn layers(&self) -> Range<usize> {
57        self.layers.clone()
58    }
59
60    /// Returns the rule's neutral placement semantics.
61    pub const fn placement(&self) -> ArchitectureStatePlacement {
62        self.placement
63    }
64}
65
66/// Complete architecture-authored mutable-state partition policy.
67///
68/// Resolution validates that the rules cover the supplied [`StateLayout`]
69/// exactly once and that unit-aligned ranges match their execution groups.
70#[derive(Debug, Clone, Eq, PartialEq)]
71pub struct ArchitectureStatePartitionPlan {
72    rules: Vec<ArchitectureStatePartitionRule>,
73}
74
75impl ArchitectureStatePartitionPlan {
76    /// Collects the architecture's state placement rules in declaration order.
77    pub fn new(rules: impl IntoIterator<Item = ArchitectureStatePartitionRule>) -> Self {
78        Self {
79            rules: rules.into_iter().collect(),
80        }
81    }
82
83    /// Returns the declared state placement rules.
84    pub fn rules(&self) -> &[ArchitectureStatePartitionRule] {
85        &self.rules
86    }
87}
88
89/// Invalid architecture-authored mutable-state partition policy.
90#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
91#[non_exhaustive]
92pub enum ArchitectureStatePartitionError {
93    /// The plan contains no placement rules.
94    #[error("architecture state partition plan must contain at least one rule")]
95    EmptyPlan,
96    /// A rule selected no state layers.
97    #[error("architecture state partition rule has empty range {start}..{end}")]
98    EmptyRange {
99        /// Inclusive invalid range start.
100        start: usize,
101        /// Exclusive invalid range end.
102        end: usize,
103    },
104    /// A rule selected state layers outside the complete layout.
105    #[error("architecture state partition range {start}..{end} exceeds the {layers}-layer layout")]
106    RangeOutOfBounds {
107        /// Inclusive invalid range start.
108        start: usize,
109        /// Exclusive invalid range end.
110        end: usize,
111        /// Complete state-layout length.
112        layers: usize,
113    },
114    /// Two rules selected the same state layer.
115    #[error(
116        "architecture state partition range starts at {start}, before prior frontier {frontier}"
117    )]
118    OverlappingRange {
119        /// Inclusive overlapping range start.
120        start: usize,
121        /// End of the preceding declared range.
122        frontier: usize,
123    },
124    /// No rule selected one or more state layers.
125    #[error("architecture state layer {layer} is not assigned by the partition plan")]
126    UnassignedLayer {
127        /// First state layer without a rule.
128        layer: usize,
129    },
130    /// A unit-aligned rule named a nonexistent execution group.
131    #[error("architecture state partition references unknown execution group {group}")]
132    UnknownGroup {
133        /// Missing canonical execution-group slot.
134        group: usize,
135    },
136    /// A unit-aligned state range and its execution group have different lengths.
137    #[error(
138        "architecture state range {start}..{end} has {} layers but group {group} has {units} units",
139        end - start
140    )]
141    GroupLengthMismatch {
142        /// Canonical execution-group slot.
143        group: usize,
144        /// Inclusive state-range start.
145        start: usize,
146        /// Exclusive state-range end.
147        end: usize,
148        /// Execution-group unit count.
149        units: usize,
150    },
151    /// This partition's selected state ranges cannot use the contiguous state representation.
152    #[error(
153        "architecture state partition selects discontiguous ranges ending at {frontier} and starting at {start}"
154    )]
155    DiscontiguousSelection {
156        /// End of the preceding selected range.
157        frontier: usize,
158        /// Start of the next selected range.
159        start: usize,
160    },
161    /// The selected state layout could not be sliced from the complete layout.
162    #[error("architecture state partition layout is invalid: {0}")]
163    InvalidLayout(String),
164}
165
166/// Stable name assigned to the implicit segment of a simple state layout.
167pub const DEFAULT_STATE_SEGMENT_ID: &str = "state";
168
169/// Validated stable identity of one contiguous mutable-state segment.
170#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
171pub struct StateSegmentId(String);
172
173impl StateSegmentId {
174    /// Creates a non-empty stable segment identity.
175    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    /// Returns the stable segment name.
184    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/// Lifetime policy attached to a named mutable-state segment.
196#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
197#[non_exhaustive]
198pub enum StateSegmentLifetime {
199    /// State survives from one model input or frame to the next.
200    Persistent,
201    /// State is reused within one frame and reset at the frame boundary.
202    FrameLocal,
203}
204
205/// One named contiguous range in an architecture's state layout.
206#[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    /// Creates a non-empty segment range.
216    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    /// Returns the stable segment identity.
245    pub const fn id(&self) -> &StateSegmentId {
246        &self.id
247    }
248
249    /// Returns the architecture-global state-layer range.
250    pub fn layers(&self) -> Range<usize> {
251        self.layers.clone()
252    }
253
254    /// Returns whether the segment persists or resets at a frame boundary.
255    pub const fn lifetime(&self) -> StateSegmentLifetime {
256        self.lifetime
257    }
258
259    /// Returns the segment's processed-token delta from the persisted prefix.
260    pub const fn processed_token_offset(&self) -> i32 {
261        self.processed_token_offset
262    }
263}
264
265/// Complete ordered mutable-state geometry owned by one model instance.
266#[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    /// Creates and validates a simple ordered layout with one persistent
275    /// segment named [`DEFAULT_STATE_SEGMENT_ID`].
276    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    /// Creates an ordered layout partitioned into named contiguous segments.
293    ///
294    /// Segment declarations are sorted into layer order and must form an exact,
295    /// non-overlapping partition of every state-bearing layer. Stable segment
296    /// identity and lifetime therefore participate in layout equality and
297    /// runtime-state compatibility checks.
298    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    /// Returns the number of architecture-global layers represented here.
364    pub fn len(&self) -> usize {
365        self.layers.len()
366    }
367
368    /// Logical host storage copied with this complete layout, including nested
369    /// component shapes and segment names. Allocator capacity/overhead is excluded.
370    /// None means checked size arithmetic overflowed, never zero storage.
371    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    /// Returns whether this layout has no layers.
400    pub fn is_empty(&self) -> bool {
401        self.layers.is_empty()
402    }
403
404    /// Returns one layer's exact state policy.
405    pub fn layer(&self, layer: usize) -> Option<&LayerCachePolicy> {
406        self.layers.get(layer)
407    }
408
409    /// Borrows the portable ordered layer schedule.
410    pub const fn layers(&self) -> &LayerSchedule<LayerCachePolicy> {
411        &self.layers
412    }
413
414    /// Returns ordered named semantic components for one layer.
415    pub fn components(&self, layer: usize) -> Option<&[StateComponentPolicy]> {
416        self.components.get(layer).map(Vec::as_slice)
417    }
418
419    /// Returns named state segments in deterministic layer order.
420    pub fn segments(&self) -> &[StateSegmentSpec] {
421        &self.segments
422    }
423
424    /// Resolves one named state segment.
425    pub fn segment(&self, id: &StateSegmentId) -> Option<&StateSegmentSpec> {
426        self.segments.iter().find(|segment| segment.id() == id)
427    }
428
429    /// Resolves the unique named segment containing one state layer.
430    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    /// Expands architecture-declared segment frontiers into layer order.
437    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    /// Selects a contiguous architecture-global range while preserving the
449    /// intersecting segment identities, lifetimes, and token frontiers.
450    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
486/// Concrete layer state capable of exposing backend-native retained tensors.
487pub trait RuntimeLayerState<B: NeuralBackend> {
488    /// Allocation-free iterator returned for one layer.
489    type RetainedValues<'a>: Iterator<Item = &'a B::Tensor>
490    where
491        Self: 'a,
492        B::Tensor: 'a;
493
494    /// Borrows tensors that must remain alive through this layer's submission.
495    fn retained_values(&self) -> Self::RetainedValues<'_>;
496}
497
498/// Reset capability for one concrete backend-native layer state.
499///
500/// Reset drops the semantic contents of the layer state without replacing its
501/// concrete cache type or inspecting backend-native values on the host.
502pub trait ResettableRuntimeLayerState<B: NeuralBackend>: RuntimeLayerState<B> {
503    /// Clears this layer state to its initial empty value.
504    fn reset(&mut self) -> Result<(), StateError>;
505}
506
507/// Mutable access to architecture-declared fixed state components.
508///
509/// Operators address semantic roles rather than backend storage. Concrete
510/// realizations keep native tensors and may combine these slots with an
511/// append-only attention cache in the same layer state.
512pub trait RuntimeStateComponents<B: NeuralBackend>: RuntimeLayerState<B> {
513    /// Current absolute token frontier for this layer.
514    fn position(&self) -> i32;
515
516    /// Borrows the optional tensor slot for one declared fixed component.
517    fn fixed_component(
518        &mut self,
519        role: StateTensorRole,
520    ) -> Result<&mut Option<B::Tensor>, StateError>;
521
522    /// Advances a fixed-state-only layer after a successful operator call.
523    fn advance_fixed(&mut self, tokens: i32) -> Result<(), StateError>;
524}
525
526/// Mutable state realization consumed by generic resident and layerwise engines.
527pub trait RuntimeState<B: NeuralBackend> {
528    /// Concrete iterator retaining native values for one execution unit.
529    type RetainedValues<'a>: Iterator<Item = &'a B::Tensor>
530    where
531        Self: 'a,
532        B::Tensor: 'a;
533
534    /// Returns the exact layout used to create this realization.
535    fn layout(&self) -> &StateLayout;
536
537    /// Returns state geometry, or `None` for an explicit rank with no mutable state.
538    ///
539    /// Existing stateful realizations inherit the strict layout. Stateless implementations
540    /// override this method so generic session control never invents sentinel state geometry.
541    fn optional_layout(&self) -> Option<&StateLayout> {
542        Some(self.layout())
543    }
544
545    /// Borrows tensors retained by one execution unit without cloning handles.
546    ///
547    /// The flat ordinal addresses policy storage while `address` preserves
548    /// architecture-group semantics for composite and shared state.
549    fn retained_values(
550        &self,
551        ordinal: usize,
552        address: crate::ExecutionUnitAddress,
553    ) -> Result<Self::RetainedValues<'_>, StateError>;
554}
555
556/// Additive backend mechanism for realizing an architecture-declared state layout.
557///
558/// Ordinary key/value architectures need not implement this extension: it is
559/// selected only by composition that requires a distinct concrete state
560/// representation, such as a layout combining attention and fixed components.
561pub trait ArchitectureStateFactory<B: NeuralBackend> {
562    /// Concrete state returned by this realization mechanism.
563    type State: RuntimeState<B>;
564    /// Backend-specific construction failure.
565    type Error;
566
567    /// Allocates native state for the exact selected architecture layout.
568    fn realize(&mut self, layout: &StateLayout) -> Result<Self::State, Self::Error>;
569}
570
571/// Failure while selecting and realizing architecture-authored mutable state.
572#[derive(Debug, thiserror::Error)]
573#[non_exhaustive]
574pub enum ArchitectureStateRealizationError<ArchitectureError, FactoryError> {
575    /// The architecture could not derive its authoritative state layout.
576    #[error("architecture state layout selection failed")]
577    Architecture(#[source] ArchitectureError),
578    /// The selected backend mechanism could not realize the layout.
579    #[error("backend state realization failed")]
580    Factory(#[source] FactoryError),
581    /// The backend returned state whose layout differs from the selected value.
582    #[error("backend state realization changed the selected architecture layout")]
583    LayoutMismatch,
584}
585
586/// Selects the architecture's exact state layout and realizes it through an
587/// explicitly supplied additive mechanism.
588pub 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
609/// Named-segment reset supported by a concrete runtime-state realization.
610pub trait ResettableRuntimeState<B: NeuralBackend>: RuntimeState<B> {
611    /// Resets every layer in exactly one declared state segment.
612    fn reset_segment(&mut self, segment: &StateSegmentId) -> Result<(), StateError>;
613}
614
615/// Optional capability for architectures with one indexed state per layer.
616pub trait LayerRuntimeState<B: NeuralBackend>: RuntimeState<B> {
617    /// Concrete monomorphized layer state used by the architecture.
618    type LayerState: RuntimeLayerState<B>;
619
620    /// Mutably borrows one architecture-global layer state.
621    fn layer(&mut self, layer: usize) -> Result<&mut Self::LayerState, StateError>;
622}
623
624/// Fully device-resident state with one concrete value per architecture layer.
625#[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    /// Realizes every layer through a backend-specific construction closure.
649    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    /// Creates an explicit rank-local realization with no mutable state slots.
667    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/// Architecture and placement identity used to derive persistence identity.
763#[derive(Debug, Clone, Eq, PartialEq)]
764pub struct ModelStateIdentity {
765    /// Stable architecture family.
766    model_family: String,
767    /// Effective normalized model type.
768    effective_model_type: String,
769    /// Cache-relevant architecture fingerprint.
770    architecture_fingerprint: String,
771    /// Total architecture layer count.
772    layer_count: usize,
773    /// Inclusive first global layer owned by this runtime instance.
774    global_layer_start: usize,
775    /// Attention sink or pinned-prefix token count.
776    sink_tokens: usize,
777    /// Rank-local distributed placement.
778    topology: PromptCacheTopology,
779}
780
781impl ModelStateIdentity {
782    /// Creates a validated architecture and placement identity.
783    #[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    /// Total architecture layer count.
822    pub const fn layer_count(&self) -> usize {
823        self.layer_count
824    }
825
826    /// Inclusive first global layer owned by this runtime instance.
827    pub const fn global_layer_start(&self) -> usize {
828        self.global_layer_start
829    }
830
831    /// Combines architecture identity, placement, and exact state geometry.
832    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/// Invalid architecture state geometry or runtime access.
863#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
864#[non_exhaustive]
865pub enum StateError {
866    /// A model declared no state-bearing layer slots.
867    #[error("runtime state layout must contain at least one layer")]
868    EmptyLayout,
869    /// A composite layout declared no named state segments.
870    #[error("runtime state layout must contain at least one named segment")]
871    EmptySegments,
872    /// A state segment identity was empty or whitespace-only.
873    #[error("runtime state segment identity must not be empty")]
874    EmptySegmentId,
875    /// A state segment range contained no layers.
876    #[error("runtime state segment {segment:?} has empty layer range {start}..{end}")]
877    EmptySegmentRange {
878        /// Invalid segment identity.
879        segment: StateSegmentId,
880        /// Inclusive range start.
881        start: usize,
882        /// Exclusive range end.
883        end: usize,
884    },
885    /// A state segment claimed to be ahead of the persisted token prefix.
886    #[error("runtime state segment {segment:?} has positive processed-token offset {offset}")]
887    PositiveSegmentOffset {
888        /// Invalid segment identity.
889        segment: StateSegmentId,
890        /// Invalid positive processed-token offset.
891        offset: i32,
892    },
893    /// A requested sub-layout range was empty or outside the source layout.
894    #[error("runtime state layout cannot select range {start}..{end} from {layers} layers")]
895    InvalidLayoutSlice {
896        /// Inclusive requested layer start.
897        start: usize,
898        /// Exclusive requested layer end.
899        end: usize,
900        /// Available source layer count.
901        layers: usize,
902    },
903    /// Two state segments used the same stable identity.
904    #[error("runtime state segment {segment:?} is declared more than once")]
905    DuplicateSegment {
906        /// Duplicated identity.
907        segment: StateSegmentId,
908    },
909    /// A state segment addressed layers outside the layout.
910    #[error(
911        "runtime state segment {segment:?} range {start}..{end} exceeds {layers} layout layers"
912    )]
913    SegmentOutOfBounds {
914        /// Invalid segment identity.
915        segment: StateSegmentId,
916        /// Inclusive range start.
917        start: usize,
918        /// Exclusive range end.
919        end: usize,
920        /// Total layout layer count.
921        layers: usize,
922    },
923    /// A state segment overlapped an earlier segment in layer order.
924    #[error(
925        "runtime state segment {segment:?} starts at layer {start}, before prior frontier {frontier}"
926    )]
927    OverlappingSegment {
928        /// Overlapping segment identity.
929        segment: StateSegmentId,
930        /// Inclusive range start.
931        start: usize,
932        /// End of the prior segment.
933        frontier: usize,
934    },
935    /// No named segment owned one layer in the state layout.
936    #[error("runtime state layer {layer} is not assigned to a named segment")]
937    UnassignedStateLayer {
938        /// First unassigned layer.
939        layer: usize,
940    },
941    /// A reset requested a segment absent from the realized layout.
942    #[error("runtime state layout has no segment {segment:?}")]
943    UnknownSegment {
944        /// Requested segment identity.
945        segment: StateSegmentId,
946    },
947    /// A concrete backend failed while clearing a declared state segment.
948    #[error("runtime state reset failed: {0}")]
949    ResetFailed(String),
950    /// One layer supplied an invalid portable state policy.
951    #[error("invalid runtime state policy for layer {layer}: {reason}")]
952    InvalidLayer {
953        /// Invalid global layer index.
954        layer: usize,
955        /// Validation detail.
956        reason: String,
957    },
958    /// A residency plan violates a finite-resource invariant.
959    #[error("invalid runtime state residency plan: {0}")]
960    InvalidResidency(String),
961    /// A runtime requested a layer outside the realized layout.
962    #[error("runtime state layer {layer} is outside the {count}-layer layout")]
963    UnknownLayer {
964        /// Requested layer.
965        layer: usize,
966        /// Realized layer count.
967        count: usize,
968    },
969    /// A layer state does not declare the requested fixed component.
970    #[error("runtime state layer does not declare fixed component {role:?}")]
971    UnknownComponent {
972        /// Requested semantic component.
973        role: StateTensorRole,
974    },
975    /// A fixed-state token frontier could not be advanced safely.
976    #[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}