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    /// Returns whether this layout has no layers.
369    pub fn is_empty(&self) -> bool {
370        self.layers.is_empty()
371    }
372
373    /// Returns one layer's exact state policy.
374    pub fn layer(&self, layer: usize) -> Option<&LayerCachePolicy> {
375        self.layers.get(layer)
376    }
377
378    /// Borrows the portable ordered layer schedule.
379    pub const fn layers(&self) -> &LayerSchedule<LayerCachePolicy> {
380        &self.layers
381    }
382
383    /// Returns ordered named semantic components for one layer.
384    pub fn components(&self, layer: usize) -> Option<&[StateComponentPolicy]> {
385        self.components.get(layer).map(Vec::as_slice)
386    }
387
388    /// Returns named state segments in deterministic layer order.
389    pub fn segments(&self) -> &[StateSegmentSpec] {
390        &self.segments
391    }
392
393    /// Resolves one named state segment.
394    pub fn segment(&self, id: &StateSegmentId) -> Option<&StateSegmentSpec> {
395        self.segments.iter().find(|segment| segment.id() == id)
396    }
397
398    /// Resolves the unique named segment containing one state layer.
399    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    /// Expands architecture-declared segment frontiers into layer order.
406    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    /// Selects a contiguous architecture-global range while preserving the
418    /// intersecting segment identities, lifetimes, and token frontiers.
419    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
455/// Concrete layer state capable of exposing backend-native retained tensors.
456pub trait RuntimeLayerState<B: NeuralBackend> {
457    /// Allocation-free iterator returned for one layer.
458    type RetainedValues<'a>: Iterator<Item = &'a B::Tensor>
459    where
460        Self: 'a,
461        B::Tensor: 'a;
462
463    /// Borrows tensors that must remain alive through this layer's submission.
464    fn retained_values(&self) -> Self::RetainedValues<'_>;
465}
466
467/// Reset capability for one concrete backend-native layer state.
468///
469/// Reset drops the semantic contents of the layer state without replacing its
470/// concrete cache type or inspecting backend-native values on the host.
471pub trait ResettableRuntimeLayerState<B: NeuralBackend>: RuntimeLayerState<B> {
472    /// Clears this layer state to its initial empty value.
473    fn reset(&mut self) -> Result<(), StateError>;
474}
475
476/// Mutable access to architecture-declared fixed state components.
477///
478/// Operators address semantic roles rather than backend storage. Concrete
479/// realizations keep native tensors and may combine these slots with an
480/// append-only attention cache in the same layer state.
481pub trait RuntimeStateComponents<B: NeuralBackend>: RuntimeLayerState<B> {
482    /// Current absolute token frontier for this layer.
483    fn position(&self) -> i32;
484
485    /// Borrows the optional tensor slot for one declared fixed component.
486    fn fixed_component(
487        &mut self,
488        role: StateTensorRole,
489    ) -> Result<&mut Option<B::Tensor>, StateError>;
490
491    /// Advances a fixed-state-only layer after a successful operator call.
492    fn advance_fixed(&mut self, tokens: i32) -> Result<(), StateError>;
493}
494
495/// Mutable state realization consumed by generic resident and layerwise engines.
496pub trait RuntimeState<B: NeuralBackend> {
497    /// Concrete iterator retaining native values for one execution unit.
498    type RetainedValues<'a>: Iterator<Item = &'a B::Tensor>
499    where
500        Self: 'a,
501        B::Tensor: 'a;
502
503    /// Returns the exact layout used to create this realization.
504    fn layout(&self) -> &StateLayout;
505
506    /// Returns state geometry, or `None` for an explicit rank with no mutable state.
507    ///
508    /// Existing stateful realizations inherit the strict layout. Stateless implementations
509    /// override this method so generic session control never invents sentinel state geometry.
510    fn optional_layout(&self) -> Option<&StateLayout> {
511        Some(self.layout())
512    }
513
514    /// Borrows tensors retained by one execution unit without cloning handles.
515    ///
516    /// The flat ordinal addresses policy storage while `address` preserves
517    /// architecture-group semantics for composite and shared state.
518    fn retained_values(
519        &self,
520        ordinal: usize,
521        address: crate::ExecutionUnitAddress,
522    ) -> Result<Self::RetainedValues<'_>, StateError>;
523}
524
525/// Additive backend mechanism for realizing an architecture-declared state layout.
526///
527/// Ordinary key/value architectures need not implement this extension: it is
528/// selected only by composition that requires a distinct concrete state
529/// representation, such as a layout combining attention and fixed components.
530pub trait ArchitectureStateFactory<B: NeuralBackend> {
531    /// Concrete state returned by this realization mechanism.
532    type State: RuntimeState<B>;
533    /// Backend-specific construction failure.
534    type Error;
535
536    /// Allocates native state for the exact selected architecture layout.
537    fn realize(&mut self, layout: &StateLayout) -> Result<Self::State, Self::Error>;
538}
539
540/// Failure while selecting and realizing architecture-authored mutable state.
541#[derive(Debug, thiserror::Error)]
542#[non_exhaustive]
543pub enum ArchitectureStateRealizationError<ArchitectureError, FactoryError> {
544    /// The architecture could not derive its authoritative state layout.
545    #[error("architecture state layout selection failed")]
546    Architecture(#[source] ArchitectureError),
547    /// The selected backend mechanism could not realize the layout.
548    #[error("backend state realization failed")]
549    Factory(#[source] FactoryError),
550    /// The backend returned state whose layout differs from the selected value.
551    #[error("backend state realization changed the selected architecture layout")]
552    LayoutMismatch,
553}
554
555/// Selects the architecture's exact state layout and realizes it through an
556/// explicitly supplied additive mechanism.
557pub 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
578/// Named-segment reset supported by a concrete runtime-state realization.
579pub trait ResettableRuntimeState<B: NeuralBackend>: RuntimeState<B> {
580    /// Resets every layer in exactly one declared state segment.
581    fn reset_segment(&mut self, segment: &StateSegmentId) -> Result<(), StateError>;
582}
583
584/// Optional capability for architectures with one indexed state per layer.
585pub trait LayerRuntimeState<B: NeuralBackend>: RuntimeState<B> {
586    /// Concrete monomorphized layer state used by the architecture.
587    type LayerState: RuntimeLayerState<B>;
588
589    /// Mutably borrows one architecture-global layer state.
590    fn layer(&mut self, layer: usize) -> Result<&mut Self::LayerState, StateError>;
591}
592
593/// Fully device-resident state with one concrete value per architecture layer.
594#[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    /// Realizes every layer through a backend-specific construction closure.
618    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    /// Creates an explicit rank-local realization with no mutable state slots.
636    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/// Architecture and placement identity used to derive persistence identity.
732#[derive(Debug, Clone, Eq, PartialEq)]
733pub struct ModelStateIdentity {
734    /// Stable architecture family.
735    model_family: String,
736    /// Effective normalized model type.
737    effective_model_type: String,
738    /// Cache-relevant architecture fingerprint.
739    architecture_fingerprint: String,
740    /// Total architecture layer count.
741    layer_count: usize,
742    /// Inclusive first global layer owned by this runtime instance.
743    global_layer_start: usize,
744    /// Attention sink or pinned-prefix token count.
745    sink_tokens: usize,
746    /// Rank-local distributed placement.
747    topology: PromptCacheTopology,
748}
749
750impl ModelStateIdentity {
751    /// Creates a validated architecture and placement identity.
752    #[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    /// Total architecture layer count.
791    pub const fn layer_count(&self) -> usize {
792        self.layer_count
793    }
794
795    /// Inclusive first global layer owned by this runtime instance.
796    pub const fn global_layer_start(&self) -> usize {
797        self.global_layer_start
798    }
799
800    /// Combines architecture identity, placement, and exact state geometry.
801    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/// Invalid architecture state geometry or runtime access.
832#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
833#[non_exhaustive]
834pub enum StateError {
835    /// A model declared no state-bearing layer slots.
836    #[error("runtime state layout must contain at least one layer")]
837    EmptyLayout,
838    /// A composite layout declared no named state segments.
839    #[error("runtime state layout must contain at least one named segment")]
840    EmptySegments,
841    /// A state segment identity was empty or whitespace-only.
842    #[error("runtime state segment identity must not be empty")]
843    EmptySegmentId,
844    /// A state segment range contained no layers.
845    #[error("runtime state segment {segment:?} has empty layer range {start}..{end}")]
846    EmptySegmentRange {
847        /// Invalid segment identity.
848        segment: StateSegmentId,
849        /// Inclusive range start.
850        start: usize,
851        /// Exclusive range end.
852        end: usize,
853    },
854    /// A state segment claimed to be ahead of the persisted token prefix.
855    #[error("runtime state segment {segment:?} has positive processed-token offset {offset}")]
856    PositiveSegmentOffset {
857        /// Invalid segment identity.
858        segment: StateSegmentId,
859        /// Invalid positive processed-token offset.
860        offset: i32,
861    },
862    /// A requested sub-layout range was empty or outside the source layout.
863    #[error("runtime state layout cannot select range {start}..{end} from {layers} layers")]
864    InvalidLayoutSlice {
865        /// Inclusive requested layer start.
866        start: usize,
867        /// Exclusive requested layer end.
868        end: usize,
869        /// Available source layer count.
870        layers: usize,
871    },
872    /// Two state segments used the same stable identity.
873    #[error("runtime state segment {segment:?} is declared more than once")]
874    DuplicateSegment {
875        /// Duplicated identity.
876        segment: StateSegmentId,
877    },
878    /// A state segment addressed layers outside the layout.
879    #[error(
880        "runtime state segment {segment:?} range {start}..{end} exceeds {layers} layout layers"
881    )]
882    SegmentOutOfBounds {
883        /// Invalid segment identity.
884        segment: StateSegmentId,
885        /// Inclusive range start.
886        start: usize,
887        /// Exclusive range end.
888        end: usize,
889        /// Total layout layer count.
890        layers: usize,
891    },
892    /// A state segment overlapped an earlier segment in layer order.
893    #[error(
894        "runtime state segment {segment:?} starts at layer {start}, before prior frontier {frontier}"
895    )]
896    OverlappingSegment {
897        /// Overlapping segment identity.
898        segment: StateSegmentId,
899        /// Inclusive range start.
900        start: usize,
901        /// End of the prior segment.
902        frontier: usize,
903    },
904    /// No named segment owned one layer in the state layout.
905    #[error("runtime state layer {layer} is not assigned to a named segment")]
906    UnassignedStateLayer {
907        /// First unassigned layer.
908        layer: usize,
909    },
910    /// A reset requested a segment absent from the realized layout.
911    #[error("runtime state layout has no segment {segment:?}")]
912    UnknownSegment {
913        /// Requested segment identity.
914        segment: StateSegmentId,
915    },
916    /// A concrete backend failed while clearing a declared state segment.
917    #[error("runtime state reset failed: {0}")]
918    ResetFailed(String),
919    /// One layer supplied an invalid portable state policy.
920    #[error("invalid runtime state policy for layer {layer}: {reason}")]
921    InvalidLayer {
922        /// Invalid global layer index.
923        layer: usize,
924        /// Validation detail.
925        reason: String,
926    },
927    /// A residency plan violates a finite-resource invariant.
928    #[error("invalid runtime state residency plan: {0}")]
929    InvalidResidency(String),
930    /// A runtime requested a layer outside the realized layout.
931    #[error("runtime state layer {layer} is outside the {count}-layer layout")]
932    UnknownLayer {
933        /// Requested layer.
934        layer: usize,
935        /// Realized layer count.
936        count: usize,
937    },
938    /// A layer state does not declare the requested fixed component.
939    #[error("runtime state layer does not declare fixed component {role:?}")]
940    UnknownComponent {
941        /// Requested semantic component.
942        role: StateTensorRole,
943    },
944    /// A fixed-state token frontier could not be advanced safely.
945    #[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}