Skip to main content

eredu_runtime/
partition.rs

1//! Backend-neutral ownership of one rank-local architecture partition.
2
3use std::ops::Deref;
4use std::{collections::BTreeMap, collections::BTreeSet, ops::Range};
5
6use crate::{
7    ArchitectureStatePartitionError, ArchitectureStatePartitionPlan, ArchitectureStatePlacement,
8    ExecutionGraph, ExecutionGroupId, ExecutionUnitLayout, LayeredForwardState,
9    LayeredPartitionInput, LayeredPartitionOutput, ParameterGroupSpec,
10    PartitionedLayeredArchitecture, RuntimeState, StateLayout,
11};
12
13/// Architecture-owned location of one neutral parameter group.
14#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
15#[non_exhaustive]
16pub enum ParameterGroupOwner {
17    /// A pinned module selected by an explicit architecture static role.
18    StaticRole(String),
19    /// A shared pinned module selected when any declared static consumer is local.
20    StaticAnyOf(Vec<String>),
21    /// One architecture-global unit in a canonical execution group.
22    #[non_exhaustive]
23    ExecutionUnit {
24        /// Canonical execution-group identity.
25        group: ExecutionGroupId,
26        /// Group-local architecture-global unit index.
27        global_unit: usize,
28    },
29}
30
31impl ParameterGroupOwner {
32    /// Creates static-module ownership with a stable, non-empty role.
33    pub fn static_role(role: impl Into<String>) -> Self {
34        Self::StaticRole(role.into())
35    }
36
37    /// Creates shared static-module ownership across explicit consumer roles.
38    pub fn static_any_of(roles: impl IntoIterator<Item = impl Into<String>>) -> Self {
39        Self::StaticAnyOf(roles.into_iter().map(Into::into).collect())
40    }
41
42    /// Creates execution-unit ownership in the architecture-global index space.
43    pub fn execution_unit(group: ExecutionGroupId, global_unit: usize) -> Self {
44        Self::ExecutionUnit { group, global_unit }
45    }
46
47    fn is_local<G, A>(&self, partition: &ArchitecturePartition<G, A>) -> bool {
48        match self {
49            Self::StaticRole(role) => partition.ownership().owns_static_role(role),
50            Self::StaticAnyOf(roles) => roles
51                .iter()
52                .any(|role| partition.ownership().owns_static_role(role)),
53            Self::ExecutionUnit { group, global_unit } => {
54                partition.owns_unit(group.as_str(), *global_unit)
55            }
56        }
57    }
58
59    fn is_local_partition_parts(
60        &self,
61        groups: &[PartitionGroup],
62        ownership: &PartitionOwnership,
63    ) -> bool {
64        match self {
65            Self::StaticRole(role) => ownership.owns_static_role(role),
66            Self::StaticAnyOf(roles) => roles.iter().any(|role| ownership.owns_static_role(role)),
67            Self::ExecutionUnit { group, global_unit } => groups
68                .iter()
69                .any(|owned| owned.group() == group && owned.contains(*global_unit)),
70        }
71    }
72
73    fn static_storage_role(&self) -> Option<&str> {
74        match self {
75            Self::StaticRole(role) => Some(role),
76            Self::StaticAnyOf(roles) => roles.first().map(String::as_str),
77            Self::ExecutionUnit { .. } => None,
78        }
79    }
80}
81
82/// One neutral parameter group tagged with its architecture-owned location.
83#[derive(Debug, Clone, Eq, PartialEq)]
84pub struct OwnedParameterGroupSpec {
85    owner: ParameterGroupOwner,
86    group: ParameterGroupSpec,
87}
88
89impl OwnedParameterGroupSpec {
90    /// Tags a group with one explicit owner.
91    pub fn new(owner: ParameterGroupOwner, group: ParameterGroupSpec) -> Self {
92        Self { owner, group }
93    }
94
95    /// Returns the architecture-owned location.
96    pub const fn owner(&self) -> &ParameterGroupOwner {
97        &self.owner
98    }
99
100    /// Returns the neutral placement group.
101    pub const fn group(&self) -> &ParameterGroupSpec {
102        &self.group
103    }
104
105    /// Consumes the tag and returns the neutral placement group.
106    pub fn into_group(self) -> ParameterGroupSpec {
107        self.group
108    }
109}
110
111impl Deref for OwnedParameterGroupSpec {
112    type Target = ParameterGroupSpec;
113
114    fn deref(&self) -> &Self::Target {
115        &self.group
116    }
117}
118
119/// Complete, validated parameter-ownership declaration for an architecture.
120#[derive(Debug, Clone, Eq, PartialEq)]
121pub struct ArchitectureParameterDescription {
122    graph: ExecutionGraph,
123    unit_layout: ExecutionUnitLayout,
124    groups: Vec<OwnedParameterGroupSpec>,
125}
126
127impl ArchitectureParameterDescription {
128    /// Validates explicit ownership against the canonical graph/layout and an
129    /// authoritative set of neutral parameter groups.
130    pub fn new(
131        graph: &ExecutionGraph,
132        layout: &ExecutionUnitLayout,
133        expected: impl IntoIterator<Item = ParameterGroupSpec>,
134        groups: impl IntoIterator<Item = OwnedParameterGroupSpec>,
135    ) -> Result<Self, ArchitectureParameterError> {
136        validate_canonical_layout(graph, layout)
137            .map_err(|error| ArchitectureParameterError::InvalidLayout(error.to_string()))?;
138        let expected = parameter_targets(expected)?;
139        let groups = groups.into_iter().collect::<Vec<_>>();
140        let mut actual = BTreeMap::new();
141        for tagged in &groups {
142            match tagged.owner() {
143                ParameterGroupOwner::StaticRole(role) => {
144                    if role.trim().is_empty() {
145                        return Err(ArchitectureParameterError::EmptyStaticRole);
146                    }
147                }
148                ParameterGroupOwner::StaticAnyOf(roles) => {
149                    if roles.is_empty() || roles.iter().any(|role| role.trim().is_empty()) {
150                        return Err(ArchitectureParameterError::EmptyStaticRole);
151                    }
152                    let unique = roles.iter().collect::<BTreeSet<_>>();
153                    if unique.len() != roles.len() {
154                        return Err(ArchitectureParameterError::DuplicateStaticRole);
155                    }
156                }
157                ParameterGroupOwner::ExecutionUnit { group, global_unit } => {
158                    let Some(group_index) = graph
159                        .groups()
160                        .iter()
161                        .position(|candidate| candidate.id() == group.as_str())
162                    else {
163                        return Err(ArchitectureParameterError::UnknownExecutionGroup(
164                            group.as_str().to_owned(),
165                        ));
166                    };
167                    let available = layout
168                        .group_range(group_index)
169                        .expect("validated canonical layout contains every group")
170                        .len();
171                    if *global_unit >= available {
172                        return Err(ArchitectureParameterError::UnitOutOfRange {
173                            group: group.as_str().to_owned(),
174                            global_unit: *global_unit,
175                            available,
176                        });
177                    }
178                }
179            }
180            for member in tagged.group().members() {
181                if let Some(previous) = actual.insert(member.target().to_owned(), tagged.owner()) {
182                    return Err(ArchitectureParameterError::DuplicateOwnership {
183                        target: member.target().to_owned(),
184                        first: previous.clone(),
185                        second: tagged.owner().clone(),
186                    });
187                }
188            }
189        }
190        let actual_targets = actual.keys().cloned().collect::<BTreeSet<_>>();
191        let expected_targets = expected.keys().cloned().collect::<BTreeSet<_>>();
192        if let Some(target) = expected_targets.difference(&actual_targets).next() {
193            return Err(ArchitectureParameterError::MissingOwnership(target.clone()));
194        }
195        if let Some(target) = actual_targets.difference(&expected_targets).next() {
196            return Err(ArchitectureParameterError::UnexpectedOwnership(
197                target.clone(),
198            ));
199        }
200        Ok(Self {
201            graph: graph.clone(),
202            unit_layout: layout.clone(),
203            groups,
204        })
205    }
206
207    /// Returns the canonical execution graph that owns these parameter groups.
208    pub const fn graph(&self) -> &ExecutionGraph {
209        &self.graph
210    }
211
212    /// Returns the canonical architecture-global execution-unit layout that
213    /// owns these parameter groups.
214    pub const fn unit_layout(&self) -> &ExecutionUnitLayout {
215        &self.unit_layout
216    }
217
218    /// Proves that this description still matches a concrete neutral architecture.
219    pub fn validate_architecture<B, S, M>(
220        &self,
221        architecture: &M,
222    ) -> Result<(), ArchitecturePartitionError>
223    where
224        B: eredu_nn::NeuralBackend,
225        S: crate::RuntimeState<B>,
226        M: crate::LayeredArchitecture<B, S>,
227        M::Error: std::fmt::Display,
228    {
229        let (graph, unit_layout) = canonical_architecture_layout::<B, S, M>(architecture)?;
230        if graph != self.graph {
231            return Err(ArchitecturePartitionError::ArchitectureGraphMismatch);
232        }
233        if unit_layout != self.unit_layout {
234            return Err(ArchitecturePartitionError::ArchitectureUnitLayoutMismatch);
235        }
236        Ok(())
237    }
238
239    /// Returns every explicitly tagged neutral parameter group.
240    pub fn groups(&self) -> &[OwnedParameterGroupSpec] {
241        &self.groups
242    }
243
244    /// Returns every physical target owned by groups with the supplied semantic role.
245    ///
246    /// Selection happens after architecture ownership has been assigned, so callers
247    /// do not need to rediscover families, aliases, or packed companions from target
248    /// name syntax.
249    pub fn targets_for_role(&self, role: crate::ParameterRole) -> BTreeSet<String> {
250        self.groups
251            .iter()
252            .filter(|owned| owned.group().role() == role)
253            .flat_map(|owned| owned.group().members())
254            .map(|member| member.target().to_owned())
255            .collect()
256    }
257
258    /// Selects rank-owned groups without discarding their architecture owner.
259    pub fn select_owned<G, A>(
260        &self,
261        partition: &ArchitecturePartition<G, A>,
262    ) -> Vec<OwnedParameterGroupSpec> {
263        self.groups
264            .iter()
265            .filter(|tagged| tagged.owner().is_local(partition))
266            .cloned()
267            .collect()
268    }
269
270    /// Returns canonical static storage roles selected for one partition.
271    ///
272    /// Shared owners always return their first declared role, while any later
273    /// roles only act as ownership consumers.
274    pub fn select_static_roles<'a, G, A>(
275        &'a self,
276        partition: &ArchitecturePartition<G, A>,
277    ) -> Vec<&'a str> {
278        self.groups
279            .iter()
280            .filter(|tagged| tagged.owner().is_local(partition))
281            .filter_map(|tagged| tagged.owner().static_storage_role())
282            .collect::<BTreeSet<_>>()
283            .into_iter()
284            .collect()
285    }
286}
287
288fn parameter_targets(
289    groups: impl IntoIterator<Item = ParameterGroupSpec>,
290) -> Result<BTreeMap<String, String>, ArchitectureParameterError> {
291    let mut targets = BTreeMap::new();
292    for group in groups {
293        for member in group.members() {
294            if let Some(previous) =
295                targets.insert(member.target().to_owned(), group.logical_name().to_owned())
296            {
297                return Err(ArchitectureParameterError::DuplicateExpectedTarget {
298                    target: member.target().to_owned(),
299                    first: previous,
300                    second: group.logical_name().to_owned(),
301                });
302            }
303        }
304    }
305    Ok(targets)
306}
307
308/// Invalid architecture-owned parameter ownership declaration.
309#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
310#[non_exhaustive]
311pub enum ArchitectureParameterError {
312    /// The supplied graph/layout is not canonical.
313    #[error("invalid architecture parameter layout: {0}")]
314    InvalidLayout(String),
315    /// A pinned parameter group has no semantic role.
316    #[error("architecture parameter static role must not be empty")]
317    EmptyStaticRole,
318    /// A shared pinned parameter repeats one consumer role.
319    #[error("architecture shared parameter owner repeats a static role")]
320    DuplicateStaticRole,
321    /// A unit owner names no canonical graph group.
322    #[error("architecture parameter owner names unknown execution group {0:?}")]
323    UnknownExecutionGroup(String),
324    /// A unit owner exceeds its canonical group size.
325    #[error("architecture parameter owner {group}:{global_unit} exceeds {available} units")]
326    UnitOutOfRange {
327        /// Canonical execution-group identity.
328        group: String,
329        /// Invalid group-local global unit.
330        global_unit: usize,
331        /// Canonical unit count.
332        available: usize,
333    },
334    /// The authoritative neutral group set itself repeats a target.
335    #[error("expected parameter target {target:?} appears in both {first:?} and {second:?}")]
336    DuplicateExpectedTarget {
337        /// Repeated physical target.
338        target: String,
339        /// First logical group.
340        first: String,
341        /// Second logical group.
342        second: String,
343    },
344    /// Two explicit owners claim one physical target.
345    #[error("parameter target {target:?} is owned by both {first:?} and {second:?}")]
346    DuplicateOwnership {
347        /// Repeated physical target.
348        target: String,
349        /// First explicit owner.
350        first: ParameterGroupOwner,
351        /// Second explicit owner.
352        second: ParameterGroupOwner,
353    },
354    /// An authoritative target was left unowned.
355    #[error("parameter target {0:?} has no architecture owner")]
356    MissingOwnership(String),
357    /// An ownership tag names a target outside the authoritative set.
358    #[error("parameter target {0:?} is not present in the authoritative parameter groups")]
359    UnexpectedOwnership(String),
360}
361
362/// Logical scalar kind carried by one architecture-owned boundary tensor.
363///
364/// `Activation` is resolved by a concrete backend to the execution dtype
365/// selected for the surrounding pipeline activation. Integer kinds are exact.
366#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
367#[non_exhaustive]
368pub enum BoundaryTensorDtype {
369    /// The selected execution activation dtype.
370    Activation,
371    /// Exact unsigned 32-bit integer values.
372    Uint32,
373    /// Exact signed 32-bit integer values.
374    Int32,
375}
376
377/// Portable floating-point dtype carried between pipeline stages.
378///
379/// This is execution transport policy, not checkpoint storage metadata. A
380/// concrete backend must lower the selected dtype to its native tensor dtype
381/// and normalize outgoing activations to it before transport.
382#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
383#[non_exhaustive]
384pub enum PipelineActivationDtype {
385    /// IEEE 16-bit floating point.
386    Float16,
387    /// Brain 16-bit floating point.
388    Bfloat16,
389    /// IEEE 32-bit floating point.
390    Float32,
391}
392
393/// Backend-neutral wire contract shared by every stage of one pipeline.
394#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
395pub struct PipelineWireContract {
396    activation_dtype: PipelineActivationDtype,
397}
398
399impl PipelineWireContract {
400    /// Declares the exact dtype used by hidden activations and auxiliary
401    /// tensors whose boundary dtype is [`BoundaryTensorDtype::Activation`].
402    pub const fn new(activation_dtype: PipelineActivationDtype) -> Self {
403        Self { activation_dtype }
404    }
405
406    /// Returns the exact floating-point dtype transported between stages.
407    pub const fn activation_dtype(self) -> PipelineActivationDtype {
408        self.activation_dtype
409    }
410}
411
412/// One symbolic dimension in an architecture-owned boundary tensor.
413#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
414#[non_exhaustive]
415pub enum BoundaryTensorDimension {
416    /// Invocation batch size.
417    Batch,
418    /// Invocation sequence length.
419    Sequence,
420    /// Positive architecture-defined extent.
421    Fixed(i32),
422}
423
424/// Semantic role, symbolic shape, and logical dtype of one boundary tensor.
425#[derive(Debug, Clone, Eq, Hash, PartialEq)]
426pub struct BoundaryTensorSpec {
427    role: String,
428    shape: Vec<BoundaryTensorDimension>,
429    dtype: BoundaryTensorDtype,
430}
431
432impl BoundaryTensorSpec {
433    /// Declares one tensor in canonical transport order.
434    pub fn new(
435        role: impl Into<String>,
436        shape: impl IntoIterator<Item = BoundaryTensorDimension>,
437        dtype: BoundaryTensorDtype,
438    ) -> Self {
439        Self {
440            role: role.into(),
441            shape: shape.into_iter().collect(),
442            dtype,
443        }
444    }
445
446    /// Declares the standard evolving batch/sequence/hidden activation.
447    pub fn primary_activation(hidden_size: i32) -> Self {
448        Self::new(
449            "hidden",
450            [
451                BoundaryTensorDimension::Batch,
452                BoundaryTensorDimension::Sequence,
453                BoundaryTensorDimension::Fixed(hidden_size),
454            ],
455            BoundaryTensorDtype::Activation,
456        )
457    }
458
459    /// Returns the stable semantic role.
460    pub fn role(&self) -> &str {
461        &self.role
462    }
463
464    /// Returns the symbolic shape.
465    pub fn shape(&self) -> &[BoundaryTensorDimension] {
466        &self.shape
467    }
468
469    /// Returns the logical scalar kind.
470    pub const fn dtype(&self) -> BoundaryTensorDtype {
471        self.dtype
472    }
473}
474
475/// One boundary tensor after invocation-dependent dimensions are resolved.
476#[derive(Debug, Clone, Eq, Hash, PartialEq)]
477pub struct ResolvedBoundaryTensorSpec {
478    role: String,
479    shape: Vec<i32>,
480    dtype: BoundaryTensorDtype,
481}
482
483impl ResolvedBoundaryTensorSpec {
484    /// Returns the stable semantic role.
485    pub fn role(&self) -> &str {
486        &self.role
487    }
488
489    /// Returns the concrete transport shape.
490    pub fn shape(&self) -> &[i32] {
491        &self.shape
492    }
493
494    /// Returns the logical scalar kind.
495    pub const fn dtype(&self) -> BoundaryTensorDtype {
496        self.dtype
497    }
498}
499
500/// Complete primary and ordered auxiliary wire schema for one architecture boundary.
501#[derive(Debug, Clone, Eq, Hash, PartialEq)]
502pub struct BoundaryWireSchema {
503    identity: &'static str,
504    primary: BoundaryTensorSpec,
505    auxiliary: Vec<BoundaryTensorSpec>,
506}
507
508impl BoundaryWireSchema {
509    /// Creates and validates an architecture-owned wire schema.
510    pub fn new(
511        identity: &'static str,
512        primary: BoundaryTensorSpec,
513        auxiliary: impl IntoIterator<Item = BoundaryTensorSpec>,
514    ) -> Result<Self, ArchitectureBoundaryError> {
515        if identity.trim().is_empty() {
516            return Err(ArchitectureBoundaryError::EmptyIdentity);
517        }
518        if primary.dtype != BoundaryTensorDtype::Activation {
519            return Err(ArchitectureBoundaryError::InvalidPrimaryDtype { boundary: identity });
520        }
521        let auxiliary = auxiliary.into_iter().collect::<Vec<_>>();
522        let mut roles = BTreeSet::new();
523        for tensor in std::iter::once(&primary).chain(&auxiliary) {
524            if tensor.role.trim().is_empty() {
525                return Err(ArchitectureBoundaryError::EmptyTensorRole { boundary: identity });
526            }
527            if !roles.insert(tensor.role.as_str()) {
528                return Err(ArchitectureBoundaryError::DuplicateTensorRole {
529                    boundary: identity,
530                    role: tensor.role.clone(),
531                });
532            }
533            if tensor.shape.is_empty() {
534                return Err(ArchitectureBoundaryError::EmptyTensorShape {
535                    boundary: identity,
536                    role: tensor.role.clone(),
537                });
538            }
539            if tensor
540                .shape
541                .iter()
542                .any(|dimension| matches!(dimension, BoundaryTensorDimension::Fixed(value) if *value <= 0))
543            {
544                return Err(ArchitectureBoundaryError::InvalidTensorDimension {
545                    boundary: identity,
546                    role: tensor.role.clone(),
547                });
548            }
549        }
550        Ok(Self {
551            identity,
552            primary,
553            auxiliary,
554        })
555    }
556
557    /// Returns the stable schema identity.
558    pub const fn identity(&self) -> &'static str {
559        self.identity
560    }
561
562    /// Returns the primary evolving activation declaration.
563    pub const fn primary(&self) -> &BoundaryTensorSpec {
564        &self.primary
565    }
566
567    /// Returns auxiliary tensor declarations in canonical transport order.
568    pub fn auxiliary(&self) -> &[BoundaryTensorSpec] {
569        &self.auxiliary
570    }
571
572    /// Resolves invocation-dependent dimensions without backend family logic.
573    pub fn resolve(
574        &self,
575        batch_size: i32,
576        sequence_length: i32,
577    ) -> Result<ResolvedBoundaryWireSchema, ArchitectureBoundaryError> {
578        self.resolve_each(
579            batch_size,
580            std::iter::repeat_n(sequence_length, 1 + self.auxiliary.len()),
581        )
582    }
583
584    /// Resolves one exact sequence extent per primary/auxiliary tensor.
585    ///
586    /// This is used by composite boundaries whose evolving internal activation
587    /// and learned side outputs have different sequence geometries. The family
588    /// supplies values in canonical schema order; the runtime only validates and
589    /// substitutes the declared symbolic dimensions.
590    pub fn resolve_each(
591        &self,
592        batch_size: i32,
593        sequence_lengths: impl IntoIterator<Item = i32>,
594    ) -> Result<ResolvedBoundaryWireSchema, ArchitectureBoundaryError> {
595        let sequence_lengths = sequence_lengths.into_iter().collect::<Vec<_>>();
596        if sequence_lengths.len() != 1 + self.auxiliary.len() {
597            return Err(ArchitectureBoundaryError::TensorCount {
598                boundary: self.identity,
599                expected: 1 + self.auxiliary.len(),
600                actual: sequence_lengths.len(),
601            });
602        }
603        if batch_size <= 0 || sequence_lengths.iter().any(|sequence| *sequence <= 0) {
604            return Err(ArchitectureBoundaryError::InvalidInvocationGeometry {
605                boundary: self.identity,
606                batch_size,
607                sequence_length: sequence_lengths
608                    .into_iter()
609                    .find(|value| *value <= 0)
610                    .unwrap_or(0),
611            });
612        }
613        let resolve = |tensor: &BoundaryTensorSpec, sequence_length| ResolvedBoundaryTensorSpec {
614            role: tensor.role.clone(),
615            shape: tensor
616                .shape
617                .iter()
618                .map(|dimension| match dimension {
619                    BoundaryTensorDimension::Batch => batch_size,
620                    BoundaryTensorDimension::Sequence => sequence_length,
621                    BoundaryTensorDimension::Fixed(value) => *value,
622                })
623                .collect(),
624            dtype: tensor.dtype,
625        };
626        let mut sequences = sequence_lengths.into_iter();
627        Ok(ResolvedBoundaryWireSchema {
628            identity: self.identity,
629            primary: resolve(
630                &self.primary,
631                sequences.next().expect("validated primary sequence"),
632            ),
633            auxiliary: self
634                .auxiliary
635                .iter()
636                .zip(sequences)
637                .map(|(tensor, sequence)| resolve(tensor, sequence))
638                .collect(),
639        })
640    }
641}
642
643/// One architecture boundary after invocation-dependent dimensions are resolved.
644#[derive(Debug, Clone, Eq, Hash, PartialEq)]
645pub struct ResolvedBoundaryWireSchema {
646    identity: &'static str,
647    primary: ResolvedBoundaryTensorSpec,
648    auxiliary: Vec<ResolvedBoundaryTensorSpec>,
649}
650
651impl ResolvedBoundaryWireSchema {
652    /// Returns the stable schema identity.
653    pub const fn identity(&self) -> &'static str {
654        self.identity
655    }
656
657    /// Returns the resolved primary evolving activation declaration.
658    pub const fn primary(&self) -> &ResolvedBoundaryTensorSpec {
659        &self.primary
660    }
661
662    /// Returns resolved auxiliary declarations in canonical transport order.
663    pub fn auxiliary(&self) -> &[ResolvedBoundaryTensorSpec] {
664        &self.auxiliary
665    }
666}
667
668/// Typed architecture-owned tensor state and wire geometry carried across one
669/// partition boundary.
670///
671/// The runtime and a backend transport may resolve and move the encoded tensor
672/// vector, but only this family schema assigns semantic roles, shape, dtype,
673/// cardinality, or reconstructs the typed value.
674pub trait ArchitectureBoundary: Sized {
675    /// Typed family value transported by this schema.
676    type Boundary<T>;
677
678    /// Stable non-empty semantic identity used in diagnostics and wire schema
679    /// validation.
680    const IDENTITY: &'static str;
681
682    /// Primary evolving activation declaration.
683    fn primary_tensor_spec(&self) -> BoundaryTensorSpec;
684
685    /// Auxiliary tensor declarations in exact encoded order.
686    fn auxiliary_tensor_specs(&self) -> Vec<BoundaryTensorSpec>;
687
688    /// Consumes this typed value into exact role-tagged transport tensors.
689    ///
690    /// Roles are assigned while the architecture-owned typed value is
691    /// decomposed; neutral execution must never reconstruct them positionally.
692    fn encode<T>(
693        &self,
694        boundary: Self::Boundary<T>,
695    ) -> Result<Vec<ArchitectureBoundaryValue<T>>, ArchitectureBoundaryError>;
696
697    /// Reconstructs the typed value from transport-order tensors.
698    fn decode<T>(&self, tensors: Vec<T>) -> Result<Self::Boundary<T>, ArchitectureBoundaryError>;
699
700    /// Returns the validated backend-neutral wire schema.
701    fn wire_schema(&self) -> Result<BoundaryWireSchema, ArchitectureBoundaryError> {
702        BoundaryWireSchema::new(
703            Self::IDENTITY,
704            self.primary_tensor_spec(),
705            self.auxiliary_tensor_specs(),
706        )
707    }
708}
709
710/// One architecture-tagged auxiliary boundary value.
711///
712/// The semantic role is assigned while the family-owned typed boundary is
713/// decomposed. Keeping it coupled to the tensor prevents a neutral executor
714/// from silently reassigning roles by positional zipping.
715#[derive(Debug, Clone, Eq, PartialEq)]
716pub struct ArchitectureBoundaryValue<T> {
717    role: String,
718    tensor: T,
719}
720
721impl<T> ArchitectureBoundaryValue<T> {
722    /// Couples a non-empty architecture role to its exact tensor value.
723    pub fn new(role: impl Into<String>, tensor: T) -> Result<Self, ArchitectureBoundaryError> {
724        let role = role.into();
725        if role.trim().is_empty() {
726            return Err(ArchitectureBoundaryError::EmptyTaggedTensorRole);
727        }
728        Ok(Self { role, tensor })
729    }
730
731    /// Architecture-owned semantic role.
732    pub fn role(&self) -> &str {
733        &self.role
734    }
735
736    /// Borrows the exact tensor assigned to this role.
737    pub const fn tensor(&self) -> &T {
738        &self.tensor
739    }
740
741    /// Decomposes this value without cloning the tensor.
742    pub fn into_parts(self) -> (String, T) {
743        (self.role, self.tensor)
744    }
745}
746
747/// Explicit declaration that an architecture partition carries no auxiliary
748/// tensors across its boundary.
749///
750/// This marker is preferable to `()` because it still participates in the
751/// typed boundary contract and rejects any unexpected transported tensor.
752#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
753pub struct NoAuxiliaryBoundary;
754
755/// Schema for an evolving decoder activation with no auxiliary tensors.
756#[derive(Debug, Clone, Copy, Eq, PartialEq)]
757pub struct NoAuxiliaryBoundarySchema {
758    hidden_size: i32,
759}
760
761impl NoAuxiliaryBoundarySchema {
762    /// Declares a standard batch/sequence/hidden activation boundary.
763    pub const fn new(hidden_size: i32) -> Self {
764        Self { hidden_size }
765    }
766}
767
768impl ArchitectureBoundary for NoAuxiliaryBoundarySchema {
769    type Boundary<T> = NoAuxiliaryBoundary;
770
771    const IDENTITY: &'static str = "none";
772
773    fn primary_tensor_spec(&self) -> BoundaryTensorSpec {
774        BoundaryTensorSpec::primary_activation(self.hidden_size)
775    }
776
777    fn auxiliary_tensor_specs(&self) -> Vec<BoundaryTensorSpec> {
778        Vec::new()
779    }
780
781    fn encode<T>(
782        &self,
783        _boundary: Self::Boundary<T>,
784    ) -> Result<Vec<ArchitectureBoundaryValue<T>>, ArchitectureBoundaryError> {
785        Ok(Vec::new())
786    }
787
788    fn decode<T>(&self, tensors: Vec<T>) -> Result<Self::Boundary<T>, ArchitectureBoundaryError> {
789        validate_boundary_tensor_count(self, &tensors)?;
790        Ok(NoAuxiliaryBoundary)
791    }
792}
793
794/// Validates the number of tensors before a family boundary decodes any
795/// positional value.
796pub fn validate_boundary_tensor_count<B, T>(
797    boundary: &B,
798    tensors: &[T],
799) -> Result<(), ArchitectureBoundaryError>
800where
801    B: ArchitectureBoundary,
802{
803    let expected = boundary.wire_schema()?.auxiliary().len();
804    let actual = tensors.len();
805    if actual != expected {
806        return Err(ArchitectureBoundaryError::TensorCount {
807            boundary: B::IDENTITY,
808            expected,
809            actual,
810        });
811    }
812    Ok(())
813}
814
815/// Invalid architecture-owned partition boundary declaration or payload.
816#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
817#[non_exhaustive]
818pub enum ArchitectureBoundaryError {
819    /// A family boundary omitted its stable identity.
820    #[error("architecture boundary identity must not be empty")]
821    EmptyIdentity,
822    /// A role-tagged family value omitted its semantic identity.
823    #[error("architecture boundary value contains an empty tensor role")]
824    EmptyTaggedTensorRole,
825    /// A family boundary assigned a non-activation dtype to its primary tensor.
826    #[error("architecture boundary {boundary:?} primary tensor must use activation dtype")]
827    InvalidPrimaryDtype {
828        /// Stable boundary identity.
829        boundary: &'static str,
830    },
831    /// A family boundary declared an empty tensor role.
832    #[error("architecture boundary {boundary:?} contains an empty tensor role")]
833    EmptyTensorRole {
834        /// Stable boundary identity.
835        boundary: &'static str,
836    },
837    /// A family boundary declared one tensor role more than once.
838    #[error("architecture boundary {boundary:?} repeats tensor role {role:?}")]
839    DuplicateTensorRole {
840        /// Stable boundary identity.
841        boundary: &'static str,
842        /// Repeated semantic tensor role.
843        role: String,
844    },
845    /// A family boundary declared a rank-zero tensor.
846    #[error("architecture boundary {boundary:?} tensor {role:?} has no dimensions")]
847    EmptyTensorShape {
848        /// Stable boundary identity.
849        boundary: &'static str,
850        /// Tensor semantic role.
851        role: String,
852    },
853    /// A family boundary declared a non-positive fixed dimension.
854    #[error("architecture boundary {boundary:?} tensor {role:?} has a non-positive dimension")]
855    InvalidTensorDimension {
856        /// Stable boundary identity.
857        boundary: &'static str,
858        /// Tensor semantic role.
859        role: String,
860    },
861    /// A caller supplied non-positive invocation dimensions.
862    #[error(
863        "architecture boundary {boundary:?} requires positive invocation geometry, got batch {batch_size} and sequence {sequence_length}"
864    )]
865    InvalidInvocationGeometry {
866        /// Stable boundary identity.
867        boundary: &'static str,
868        /// Invalid batch size.
869        batch_size: i32,
870        /// Invalid sequence length.
871        sequence_length: i32,
872    },
873    /// A transported payload has the wrong tensor cardinality.
874    #[error(
875        "architecture boundary {boundary:?} expected {expected} tensors but received {actual}"
876    )]
877    TensorCount {
878        /// Stable boundary identity.
879        boundary: &'static str,
880        /// Declared tensor count.
881        expected: usize,
882        /// Transported tensor count.
883        actual: usize,
884    },
885    /// Family-specific boundary validation failed.
886    #[error("architecture boundary {boundary:?} is invalid: {detail}")]
887    Invalid {
888        /// Stable boundary identity.
889        boundary: &'static str,
890        /// Family-owned failure detail.
891        detail: String,
892    },
893}
894
895/// Input, output, and pinned static-module ownership for one partition.
896#[derive(Debug, Clone, Eq, PartialEq)]
897pub struct PartitionOwnership {
898    input: bool,
899    output: bool,
900    static_roles: Vec<String>,
901}
902
903impl PartitionOwnership {
904    /// Creates validated boundary and static-module ownership.
905    pub fn new(
906        input: bool,
907        output: bool,
908        static_roles: impl IntoIterator<Item = impl Into<String>>,
909    ) -> Result<Self, ArchitecturePartitionError> {
910        let static_roles = static_roles.into_iter().map(Into::into).collect::<Vec<_>>();
911        let mut unique = BTreeSet::new();
912        for role in &static_roles {
913            if role.trim().is_empty() {
914                return Err(ArchitecturePartitionError::EmptyStaticRole);
915            }
916            if !unique.insert(role.clone()) {
917                return Err(ArchitecturePartitionError::DuplicateStaticRole(
918                    role.clone(),
919                ));
920            }
921        }
922        Ok(Self {
923            input,
924            output,
925            static_roles,
926        })
927    }
928
929    /// Returns whether this partition owns model input preparation.
930    pub const fn owns_input(&self) -> bool {
931        self.input
932    }
933
934    /// Returns whether this partition owns model output production.
935    pub const fn owns_output(&self) -> bool {
936        self.output
937    }
938
939    /// Returns pinned static roles in architecture declaration order.
940    pub fn static_roles(&self) -> &[String] {
941        &self.static_roles
942    }
943
944    /// Returns whether this partition owns a named pinned static role.
945    pub fn owns_static_role(&self, role: &str) -> bool {
946        self.static_roles.iter().any(|candidate| candidate == role)
947    }
948}
949
950/// Rank-local mutable-state geometry and its architecture-global layer range.
951#[derive(Debug, Clone, Eq, PartialEq)]
952pub struct PartitionState {
953    layout: StateLayout,
954    global_layers: Range<usize>,
955}
956
957impl PartitionState {
958    /// Attaches a local state layout at one architecture-global layer offset.
959    pub fn new(
960        layout: StateLayout,
961        global_layer_offset: usize,
962    ) -> Result<Self, ArchitecturePartitionError> {
963        let end = global_layer_offset.checked_add(layout.len()).ok_or(
964            ArchitecturePartitionError::StateOffsetOverflow {
965                offset: global_layer_offset,
966                layers: layout.len(),
967            },
968        )?;
969        Ok(Self {
970            layout,
971            global_layers: global_layer_offset..end,
972        })
973    }
974
975    /// Returns the exact rank-local state layout.
976    pub const fn layout(&self) -> &StateLayout {
977        &self.layout
978    }
979
980    /// Returns the first architecture-global layer represented by the layout.
981    pub const fn global_layer_offset(&self) -> usize {
982        self.global_layers.start
983    }
984
985    /// Returns the architecture-global state-layer range.
986    pub fn global_layers(&self) -> Range<usize> {
987        self.global_layers.clone()
988    }
989
990    /// Derives prompt-cache identity from this canonical state partition.
991    pub fn prompt_cache_identity<B, M>(
992        &self,
993        architecture: &M,
994        topology: eredu_core::cache::PromptCacheTopology,
995    ) -> Result<eredu_core::cache::PromptCacheModelIdentity, ArchitecturePartitionError>
996    where
997        B: eredu_nn::NeuralBackend,
998        M: crate::ArchitectureParameters<B>,
999        M::DefinitionError: std::fmt::Display,
1000    {
1001        architecture
1002            .state_identity(self, topology)
1003            .map_err(|error| ArchitecturePartitionError::ArchitectureState(error.to_string()))?
1004            .prompt_cache_identity(self.layout())
1005            .map_err(|error| ArchitecturePartitionError::PromptCacheIdentity(error.to_string()))
1006    }
1007}
1008
1009/// One validated architecture group and its group-local global unit range.
1010#[derive(Debug, Clone, Eq, PartialEq)]
1011pub struct PartitionGroup {
1012    group: ExecutionGroupId,
1013    group_index: usize,
1014    global_units: Range<usize>,
1015}
1016
1017impl PartitionGroup {
1018    /// Returns the canonical execution-group identity.
1019    pub const fn group(&self) -> &ExecutionGroupId {
1020        &self.group
1021    }
1022
1023    /// Returns the canonical execution-group slot.
1024    pub const fn group_index(&self) -> usize {
1025        self.group_index
1026    }
1027
1028    /// Returns owned unit indices in the architecture group's global index space.
1029    pub fn global_units(&self) -> Range<usize> {
1030        self.global_units.clone()
1031    }
1032
1033    /// Returns whether the range contains one group-local global unit index.
1034    pub fn contains(&self, global_unit: usize) -> bool {
1035        self.global_units.contains(&global_unit)
1036    }
1037}
1038
1039/// Complete backend-neutral realization of one rank's architecture ownership.
1040///
1041/// `G` is family-owned local construction geometry. `A` is the family-owned
1042/// primary and auxiliary wire schema carried by the partition realization.
1043#[derive(Debug, Clone)]
1044pub struct ArchitecturePartition<G, A> {
1045    graph: ExecutionGraph,
1046    unit_layout: ExecutionUnitLayout,
1047    groups: Vec<PartitionGroup>,
1048    ownership: PartitionOwnership,
1049    state: Option<PartitionState>,
1050    local_geometry: G,
1051    boundary_schema: A,
1052    parameter_bindings: Vec<OwnedParameterGroupSpec>,
1053}
1054
1055impl<G, A> ArchitecturePartition<G, A> {
1056    /// Creates a partition from the topology declared by one concrete neutral
1057    /// architecture.
1058    ///
1059    /// This constructor derives the graph and unit layout from `architecture`,
1060    /// preventing a backend realization from publishing a parallel topology
1061    /// that merely resembles, but is not the canonical topology of, the
1062    /// architecture it will execute. Pre-allocation selection should use
1063    /// [`Self::from_description`] with the architecture-authored declaration.
1064    #[allow(clippy::too_many_arguments)]
1065    pub fn from_architecture<B, S, M, N>(
1066        architecture: &M,
1067        group_ranges: impl IntoIterator<Item = (N, Range<usize>)>,
1068        ownership: PartitionOwnership,
1069        local_geometry: G,
1070        boundary_schema: A,
1071        parameters: &ArchitectureParameterDescription,
1072    ) -> Result<Self, ArchitecturePartitionError>
1073    where
1074        B: eredu_nn::NeuralBackend,
1075        S: crate::RuntimeState<B>,
1076        M: crate::LayeredArchitecture<B, S>,
1077        M::Error: std::fmt::Display,
1078        N: Into<String>,
1079        A: ArchitectureBoundary,
1080    {
1081        let (graph, unit_layout) = canonical_architecture_layout::<B, S, M>(architecture)?;
1082        boundary_schema.wire_schema()?;
1083        if parameters.graph() != &graph {
1084            return Err(ArchitecturePartitionError::ArchitectureGraphMismatch);
1085        }
1086        if parameters.unit_layout() != &unit_layout {
1087            return Err(ArchitecturePartitionError::ArchitectureUnitLayoutMismatch);
1088        }
1089        let complete_state = architecture
1090            .state_layout()
1091            .map_err(|error| ArchitecturePartitionError::ArchitectureState(error.to_string()))?;
1092        let plan = architecture.state_partition_plan(&complete_state);
1093        let mut partition = Self::new(
1094            graph,
1095            unit_layout,
1096            group_ranges,
1097            ownership,
1098            None,
1099            local_geometry,
1100            boundary_schema,
1101            std::iter::empty(),
1102        )?;
1103        partition.state = partition
1104            .resolve_state_partition(&complete_state, &plan)
1105            .map_err(|error| ArchitecturePartitionError::ArchitectureState(error.to_string()))?;
1106        partition.parameter_bindings = parameters.select_owned(&partition);
1107        Ok(partition)
1108    }
1109
1110    /// Creates an authoritative partition from architecture-authored static declarations.
1111    ///
1112    /// This path exists for pre-materialization selection: it validates and consumes the
1113    /// canonical graph/unit declaration, state plan, boundary schema, local geometry, and
1114    /// parameter topology without constructing a backend module merely to rediscover those
1115    /// facts. Concrete backends must not synthesize any of these inputs.
1116    #[allow(clippy::too_many_arguments)]
1117    pub fn from_description<N>(
1118        parameters: &ArchitectureParameterDescription,
1119        group_ranges: impl IntoIterator<Item = (N, Range<usize>)>,
1120        ownership: PartitionOwnership,
1121        complete_state: &StateLayout,
1122        state_plan: &ArchitectureStatePartitionPlan,
1123        local_geometry: G,
1124        boundary_schema: A,
1125    ) -> Result<Self, ArchitecturePartitionError>
1126    where
1127        N: Into<String>,
1128        A: ArchitectureBoundary,
1129    {
1130        let graph = parameters.graph().clone();
1131        let unit_layout = parameters.unit_layout().clone();
1132        validate_canonical_layout(&graph, &unit_layout)?;
1133        boundary_schema.wire_schema()?;
1134        let mut partition = Self::new(
1135            graph,
1136            unit_layout,
1137            group_ranges,
1138            ownership,
1139            None,
1140            local_geometry,
1141            boundary_schema,
1142            std::iter::empty(),
1143        )?;
1144        partition.state = partition
1145            .resolve_state_partition(complete_state, state_plan)
1146            .map_err(|error| ArchitecturePartitionError::ArchitectureState(error.to_string()))?;
1147        partition.parameter_bindings = parameters.select_owned(&partition);
1148        Ok(partition)
1149    }
1150
1151    /// Creates one validated rank-local architecture partition after the
1152    /// authoritative architecture topology has already been derived.
1153    #[allow(clippy::too_many_arguments)]
1154    fn new<S>(
1155        graph: ExecutionGraph,
1156        unit_layout: ExecutionUnitLayout,
1157        group_ranges: impl IntoIterator<Item = (S, Range<usize>)>,
1158        ownership: PartitionOwnership,
1159        state: Option<PartitionState>,
1160        local_geometry: G,
1161        boundary_schema: A,
1162        parameter_bindings: impl IntoIterator<Item = OwnedParameterGroupSpec>,
1163    ) -> Result<Self, ArchitecturePartitionError>
1164    where
1165        S: Into<String>,
1166    {
1167        validate_canonical_layout(&graph, &unit_layout)?;
1168        let mut seen_groups = BTreeSet::new();
1169        let mut groups = Vec::new();
1170        for (group, global_units) in group_ranges {
1171            let group = group.into();
1172            let group_index = graph
1173                .groups()
1174                .iter()
1175                .position(|candidate| candidate.id() == group)
1176                .ok_or_else(|| ArchitecturePartitionError::UnknownGroup(group.clone()))?;
1177            if !seen_groups.insert(group.clone()) {
1178                return Err(ArchitecturePartitionError::DuplicateGroup(group));
1179            }
1180            if global_units.is_empty() {
1181                return Err(ArchitecturePartitionError::EmptyGroupRange { group });
1182            }
1183            let available = unit_layout
1184                .group_range(group_index)
1185                .expect("canonical layout contains every graph group")
1186                .len();
1187            if global_units.end > available {
1188                return Err(ArchitecturePartitionError::GroupRangeOutOfBounds {
1189                    group,
1190                    start: global_units.start,
1191                    end: global_units.end,
1192                    available,
1193                });
1194            }
1195            groups.push(PartitionGroup {
1196                group: unit_layout
1197                    .group_id(group_index)
1198                    .expect("canonical layout contains every graph group identity")
1199                    .clone(),
1200                group_index,
1201                global_units,
1202            });
1203        }
1204        groups.sort_by_key(PartitionGroup::group_index);
1205
1206        let parameter_bindings = parameter_bindings.into_iter().collect::<Vec<_>>();
1207        let mut targets = BTreeSet::new();
1208        for binding in &parameter_bindings {
1209            if !binding
1210                .owner()
1211                .is_local_partition_parts(&groups, &ownership)
1212            {
1213                return Err(ArchitecturePartitionError::NonLocalParameterOwner(
1214                    binding.owner().clone(),
1215                ));
1216            }
1217            for member in binding.members() {
1218                if !targets.insert(member.target().to_owned()) {
1219                    return Err(ArchitecturePartitionError::DuplicateParameterTarget(
1220                        member.target().to_owned(),
1221                    ));
1222                }
1223            }
1224        }
1225
1226        Ok(Self {
1227            graph,
1228            unit_layout,
1229            groups,
1230            ownership,
1231            state,
1232            local_geometry,
1233            boundary_schema,
1234            parameter_bindings,
1235        })
1236    }
1237
1238    /// Returns the canonical architecture execution graph.
1239    pub const fn graph(&self) -> &ExecutionGraph {
1240        &self.graph
1241    }
1242
1243    /// Returns the canonical complete execution-unit layout.
1244    pub const fn unit_layout(&self) -> &ExecutionUnitLayout {
1245        &self.unit_layout
1246    }
1247
1248    /// Returns groups and group-local global unit ranges owned by this rank.
1249    pub fn groups(&self) -> &[PartitionGroup] {
1250        &self.groups
1251    }
1252
1253    /// Traverses rank-owned execution units in canonical architecture order.
1254    pub fn units(&self) -> impl Iterator<Item = crate::ExecutionUnitAddress> + '_ {
1255        self.groups.iter().flat_map(move |owned| {
1256            let group = owned.group_index;
1257            let base = self
1258                .unit_layout
1259                .group_range(group)
1260                .expect("partition group belongs to its canonical layout")
1261                .start;
1262            owned.global_units.clone().map(move |index| {
1263                self.unit_layout
1264                    .address(base + index)
1265                    .expect("partition unit belongs to its canonical layout")
1266            })
1267        })
1268    }
1269
1270    /// Returns whether this rank owns one group-local global unit.
1271    pub fn owns_unit(&self, group: &str, global_unit: usize) -> bool {
1272        self.groups
1273            .iter()
1274            .any(|owned| owned.group.as_str() == group && owned.contains(global_unit))
1275    }
1276
1277    /// Returns input, output, and static-module ownership.
1278    pub const fn ownership(&self) -> &PartitionOwnership {
1279        &self.ownership
1280    }
1281
1282    /// Returns rank-local state geometry when this partition owns mutable state.
1283    pub const fn state(&self) -> Option<&PartitionState> {
1284        self.state.as_ref()
1285    }
1286
1287    /// Derives prompt-cache identity from this partition's canonical state.
1288    pub fn prompt_cache_identity<B, M>(
1289        &self,
1290        architecture: &M,
1291        topology: eredu_core::cache::PromptCacheTopology,
1292    ) -> Result<eredu_core::cache::PromptCacheModelIdentity, ArchitecturePartitionError>
1293    where
1294        B: eredu_nn::NeuralBackend,
1295        M: crate::ArchitectureParameters<B>,
1296        M::DefinitionError: std::fmt::Display,
1297    {
1298        let state = self
1299            .state()
1300            .ok_or(ArchitecturePartitionError::MissingArchitectureState)?;
1301        state.prompt_cache_identity::<B, M>(architecture, topology)
1302    }
1303
1304    /// Resolves the architecture-authored state plan for this realized partition.
1305    ///
1306    /// The current partition representation stores one contiguous global state
1307    /// interval. A valid plan may describe multiple semantic ranges, but the
1308    /// ranges selected by any one partition must be adjacent.
1309    pub fn resolve_state_partition(
1310        &self,
1311        complete: &StateLayout,
1312        plan: &ArchitectureStatePartitionPlan,
1313    ) -> Result<Option<PartitionState>, ArchitectureStatePartitionError> {
1314        if plan.rules().is_empty() {
1315            return Err(ArchitectureStatePartitionError::EmptyPlan);
1316        }
1317
1318        let mut rules = plan.rules().iter().collect::<Vec<_>>();
1319        rules.sort_by_key(|rule| rule.layers().start);
1320        let mut frontier = 0usize;
1321        for rule in &rules {
1322            let layers = rule.layers();
1323            if layers.is_empty() {
1324                return Err(ArchitectureStatePartitionError::EmptyRange {
1325                    start: layers.start,
1326                    end: layers.end,
1327                });
1328            }
1329            if layers.end > complete.len() {
1330                return Err(ArchitectureStatePartitionError::RangeOutOfBounds {
1331                    start: layers.start,
1332                    end: layers.end,
1333                    layers: complete.len(),
1334                });
1335            }
1336            if layers.start < frontier {
1337                return Err(ArchitectureStatePartitionError::OverlappingRange {
1338                    start: layers.start,
1339                    frontier,
1340                });
1341            }
1342            if layers.start > frontier {
1343                return Err(ArchitectureStatePartitionError::UnassignedLayer { layer: frontier });
1344            }
1345            if let ArchitectureStatePlacement::GroupUnits { group } = rule.placement() {
1346                let units = self
1347                    .unit_layout
1348                    .group_range(group)
1349                    .ok_or(ArchitectureStatePartitionError::UnknownGroup { group })?
1350                    .len();
1351                if layers.len() != units {
1352                    return Err(ArchitectureStatePartitionError::GroupLengthMismatch {
1353                        group,
1354                        start: layers.start,
1355                        end: layers.end,
1356                        units,
1357                    });
1358                }
1359            }
1360            frontier = layers.end;
1361        }
1362        if frontier != complete.len() {
1363            return Err(ArchitectureStatePartitionError::UnassignedLayer { layer: frontier });
1364        }
1365
1366        let mut selected = Vec::new();
1367        for rule in plan.rules() {
1368            let layers = rule.layers();
1369            match rule.placement() {
1370                ArchitectureStatePlacement::GroupUnits { group } => {
1371                    if let Some(owned) = self
1372                        .groups
1373                        .iter()
1374                        .find(|owned| owned.group_index() == group)
1375                    {
1376                        let units = owned.global_units();
1377                        selected.push(layers.start + units.start..layers.start + units.end);
1378                    }
1379                }
1380                ArchitectureStatePlacement::OutputOwner if self.ownership.owns_output() => {
1381                    selected.push(layers);
1382                }
1383                ArchitectureStatePlacement::OutputOwner => {}
1384            }
1385        }
1386        if selected.is_empty() {
1387            return Ok(None);
1388        }
1389        selected.sort_by_key(|layers| layers.start);
1390        let start = selected[0].start;
1391        let mut end = selected[0].end;
1392        for layers in selected.iter().skip(1) {
1393            if layers.start != end {
1394                return Err(ArchitectureStatePartitionError::DiscontiguousSelection {
1395                    frontier: end,
1396                    start: layers.start,
1397                });
1398            }
1399            end = layers.end;
1400        }
1401        let layout = complete
1402            .slice(start..end)
1403            .map_err(|error| ArchitectureStatePartitionError::InvalidLayout(error.to_string()))?;
1404        PartitionState::new(layout, start)
1405            .map(Some)
1406            .map_err(|error| ArchitectureStatePartitionError::InvalidLayout(error.to_string()))
1407    }
1408
1409    /// Returns family-owned rank-local construction geometry.
1410    pub const fn local_geometry(&self) -> &G {
1411        &self.local_geometry
1412    }
1413
1414    /// Returns the family-owned primary and auxiliary boundary schema.
1415    pub const fn boundary_schema(&self) -> &A {
1416        &self.boundary_schema
1417    }
1418
1419    /// Mutably returns the family-owned primary and auxiliary boundary schema.
1420    pub fn boundary_schema_mut(&mut self) -> &mut A {
1421        &mut self.boundary_schema
1422    }
1423
1424    /// Returns neutral semantic parameter bindings owned by this rank.
1425    pub fn parameter_bindings(&self) -> &[OwnedParameterGroupSpec] {
1426        &self.parameter_bindings
1427    }
1428
1429    /// Returns the exact neutral groups assigned to one architecture owner.
1430    pub fn parameter_bindings_for_owner<'a>(
1431        &'a self,
1432        owner: &'a ParameterGroupOwner,
1433    ) -> impl Iterator<Item = &'a ParameterGroupSpec> + 'a {
1434        self.parameter_bindings
1435            .iter()
1436            .filter(move |binding| binding.owner() == owner)
1437            .map(OwnedParameterGroupSpec::group)
1438    }
1439
1440    /// Proves that this partition still describes the supplied concrete
1441    /// neutral architecture.
1442    ///
1443    /// Loaders may use this when a partition crosses a backend boundary or is
1444    /// restored from a prepared plan. Both dependency edges and exact unit
1445    /// counts are compared; matching group names alone are insufficient.
1446    pub fn validate_architecture<B, S, M>(
1447        &self,
1448        architecture: &M,
1449    ) -> Result<(), ArchitecturePartitionError>
1450    where
1451        B: eredu_nn::NeuralBackend,
1452        S: crate::RuntimeState<B>,
1453        M: crate::LayeredArchitecture<B, S>,
1454        M::Error: std::fmt::Display,
1455    {
1456        let (graph, unit_layout) = canonical_architecture_layout::<B, S, M>(architecture)?;
1457        if graph != self.graph {
1458            return Err(ArchitecturePartitionError::ArchitectureGraphMismatch);
1459        }
1460        if unit_layout != self.unit_layout {
1461            return Err(ArchitecturePartitionError::ArchitectureUnitLayoutMismatch);
1462        }
1463        Ok(())
1464    }
1465}
1466
1467/// Validated execution metadata for one rank-local layered partition.
1468///
1469/// This driver is the single owner of partition input/output checks, canonical
1470/// storage and state ranges, execution-group setup/completion, and final output
1471/// projection. Concrete backends retain only state storage and unit residency.
1472#[derive(Debug, Clone)]
1473pub struct LayeredPartitionDriver {
1474    group: usize,
1475    range: Range<usize>,
1476    state_layout: Option<StateLayout>,
1477    owns_input: bool,
1478    owns_output: bool,
1479}
1480
1481impl LayeredPartitionDriver {
1482    /// Validates a canonical partition against its concrete unit storage.
1483    pub fn new<G, A>(
1484        partition: &ArchitecturePartition<G, A>,
1485        group_index: usize,
1486        storage_range: Range<usize>,
1487    ) -> Result<Self, LayeredPartitionError> {
1488        Self::new_with_state_ownership(partition, group_index, storage_range, true)
1489    }
1490
1491    /// Validates a partition while explicitly declaring whether this group owns state slots.
1492    ///
1493    /// Architecture selection must pass `false` for parameter-only composite roots. A rank can
1494    /// still own decoder state for another local group without falsely comparing that state range
1495    /// with this group's unrelated unit indices.
1496    pub fn new_with_state_ownership<G, A>(
1497        partition: &ArchitecturePartition<G, A>,
1498        group_index: usize,
1499        storage_range: Range<usize>,
1500        group_owns_state: bool,
1501    ) -> Result<Self, LayeredPartitionError> {
1502        let group = partition
1503            .groups()
1504            .iter()
1505            .find(|group| group.group_index() == group_index)
1506            .ok_or(LayeredPartitionError::GroupNotOwned { group: group_index })?;
1507        let range = group.global_units();
1508        if storage_range != range {
1509            return Err(LayeredPartitionError::StorageRange {
1510                storage: storage_range,
1511                partition: range,
1512            });
1513        }
1514        if group_owns_state {
1515            let state = partition
1516                .state()
1517                .ok_or(LayeredPartitionError::MissingState)?;
1518            if state.global_layers().start > range.start || state.global_layers().end < range.end {
1519                return Err(LayeredPartitionError::StateRange {
1520                    state: state.global_layers(),
1521                    partition: range,
1522                });
1523            }
1524        }
1525        Ok(Self {
1526            group: group.group_index(),
1527            range,
1528            state_layout: group_owns_state
1529                .then(|| partition.state().map(|state| state.layout().clone()))
1530                .flatten(),
1531            owns_input: partition.ownership().owns_input(),
1532            owns_output: partition.ownership().owns_output(),
1533        })
1534    }
1535
1536    /// Returns the canonical group-local global unit range.
1537    pub fn range(&self) -> Range<usize> {
1538        self.range.clone()
1539    }
1540
1541    /// Returns the canonical architecture execution-group slot.
1542    pub const fn group_index(&self) -> usize {
1543        self.group
1544    }
1545
1546    /// Returns state geometry for a driver created through the strict stateful constructor.
1547    pub fn state_layout(&self) -> &StateLayout {
1548        self.state_layout
1549            .as_ref()
1550            .expect("state_layout requires a state-owning layered partition driver")
1551    }
1552
1553    /// Returns rank-local state geometry, or `None` for stateless roots and ranks.
1554    pub const fn optional_state_layout(&self) -> Option<&StateLayout> {
1555        self.state_layout.as_ref()
1556    }
1557
1558    /// Returns whether this partition receives request ingress directly.
1559    pub const fn owns_input(&self) -> bool {
1560        self.owns_input
1561    }
1562
1563    /// Returns whether this partition owns architecture output projection.
1564    pub const fn owns_output(&self) -> bool {
1565        self.owns_output
1566    }
1567
1568    /// Validates input form against architecture boundary ownership.
1569    pub fn input<'a, T, A>(
1570        &self,
1571        input: LayeredPartitionInput<'a, T, A>,
1572    ) -> Result<LayeredPartitionInput<'a, T, A>, LayeredPartitionError> {
1573        match (&input, self.owns_input) {
1574            (LayeredPartitionInput::Tokens(_), true)
1575            | (LayeredPartitionInput::Hidden { .. }, _) => Ok(input),
1576            (LayeredPartitionInput::Tokens(_), false) => {
1577                Err(LayeredPartitionError::TokensOnNonInputOwner)
1578            }
1579        }
1580    }
1581
1582    /// Transports this partition's realized boundary through the selected
1583    /// opaque collective group.
1584    ///
1585    /// Keeping the operation on the validated driver makes boundary movement
1586    /// part of partition execution rather than an unrelated backend call.
1587    pub fn exchange_boundary<B>(
1588        &self,
1589        value: B::Tensor,
1590        group: &B::Group,
1591        executor: &B::Executor,
1592    ) -> Result<B::Tensor, B::CollectiveError>
1593    where
1594        B: crate::CollectiveBackend,
1595    {
1596        B::all_to_all(value, group, executor)
1597    }
1598
1599    /// Prepares the partition and starts its canonical execution group.
1600    #[allow(
1601        clippy::too_many_arguments,
1602        clippy::type_complexity,
1603        reason = "the result preserves the concrete architecture error without erased dispatch"
1604    )]
1605    pub fn begin<'a, B, S, M>(
1606        &self,
1607        architecture: &mut M,
1608        input: LayeredPartitionInput<
1609            'a,
1610            B::Tensor,
1611            <M::Boundary as ArchitectureBoundary>::Boundary<B::Tensor>,
1612        >,
1613        mask: Option<&B::Tensor>,
1614        state: &mut S,
1615        parallel: Option<&B::ParallelContext>,
1616        context: &<B::Tensor as eredu_nn::Tensor>::Context,
1617    ) -> Result<
1618        LayeredForwardState<B::Tensor, M::ForwardContext>,
1619        LayeredPartitionBeginError<M::Error>,
1620    >
1621    where
1622        B: eredu_nn::NeuralBackend,
1623        S: RuntimeState<B>,
1624        M: PartitionedLayeredArchitecture<B, S>,
1625        M::Error: std::fmt::Display,
1626    {
1627        let expected = self
1628            .state_layout
1629            .as_ref()
1630            .ok_or(LayeredPartitionBeginError::MissingState { group: self.group })?;
1631        // `state` is the partition-local allocation selected by `PartitionState`.
1632        // Global ownership is carried separately by that partition's offset, so
1633        // architecture code must index this allocation from local ordinal zero.
1634        let mut forward = match parallel {
1635            Some(parallel) => architecture
1636                .begin_partition_parallel(input, mask, state, expected, 0, parallel, context),
1637            None => architecture.begin_partition(input, mask, state, expected, 0, context),
1638        }
1639        .map_err(LayeredPartitionBeginError::Architecture)?;
1640        forward.hidden = architecture
1641            .enter_partition_group(
1642                self.group,
1643                &forward.hidden,
1644                state,
1645                &mut forward.context,
1646                parallel,
1647                context,
1648            )
1649            .map_err(LayeredPartitionBeginError::Architecture)?;
1650        Ok(forward)
1651    }
1652
1653    /// Completes the canonical group and applies output projection only on its owner.
1654    #[allow(
1655        clippy::too_many_arguments,
1656        clippy::type_complexity,
1657        reason = "the signature exposes the backend and architecture boundary types explicitly"
1658    )]
1659    pub fn finish<B, S, M>(
1660        &self,
1661        architecture: &mut M,
1662        hidden: &B::Tensor,
1663        state: &mut S,
1664        forward: &mut M::ForwardContext,
1665        parallel: Option<&B::ParallelContext>,
1666        context: &<B::Tensor as eredu_nn::Tensor>::Context,
1667    ) -> Result<
1668        LayeredPartitionOutput<
1669            B::Tensor,
1670            <M::Boundary as ArchitectureBoundary>::Boundary<B::Tensor>,
1671        >,
1672        M::Error,
1673    >
1674    where
1675        B: eredu_nn::NeuralBackend,
1676        S: RuntimeState<B>,
1677        M: PartitionedLayeredArchitecture<B, S>,
1678    {
1679        let hidden = architecture
1680            .leave_partition_group(self.group, hidden, state, forward, parallel, context)?;
1681        architecture.finish_partition(&hidden, state, forward, self.owns_output, parallel, context)
1682    }
1683}
1684
1685/// Failure to enter one concrete rank-local partition group.
1686#[derive(Debug, thiserror::Error)]
1687pub enum LayeredPartitionBeginError<E>
1688where
1689    E: std::fmt::Display,
1690{
1691    /// This group was declared stateless and cannot use the stateful partition entry API.
1692    #[error("stateless partition group {group} requires an architecture stateless entry strategy")]
1693    MissingState {
1694        /// Canonical architecture group slot.
1695        group: usize,
1696    },
1697    /// Architecture-owned partition entry failed.
1698    #[error("partition architecture entry failed: {0}")]
1699    Architecture(E),
1700}
1701
1702/// Invalid concrete realization or boundary use of a layered partition.
1703#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
1704#[non_exhaustive]
1705pub enum LayeredPartitionError {
1706    /// The selected architecture execution group is not owned by this partition.
1707    #[error("layered partition does not own execution group {group}")]
1708    GroupNotOwned {
1709        /// Canonical architecture group index.
1710        group: usize,
1711    },
1712    /// Concrete unit storage does not match canonical ownership.
1713    #[error("partition storage range {storage:?} disagrees with canonical range {partition:?}")]
1714    StorageRange {
1715        /// Concrete backend storage range.
1716        storage: Range<usize>,
1717        /// Canonical partition range.
1718        partition: Range<usize>,
1719    },
1720    /// Partition omitted mutable state geometry.
1721    #[error("layered partition has no runtime state")]
1722    MissingState,
1723    /// Mutable state geometry does not match canonical unit ownership.
1724    #[error("partition state range {state:?} disagrees with canonical range {partition:?}")]
1725    StateRange {
1726        /// Architecture-global state range.
1727        state: Range<usize>,
1728        /// Canonical partition range.
1729        partition: Range<usize>,
1730    },
1731    /// Token ids were supplied after the architecture input boundary.
1732    #[error("non-input partition received token ids")]
1733    TokensOnNonInputOwner,
1734}
1735
1736fn canonical_architecture_layout<B, S, M>(
1737    architecture: &M,
1738) -> Result<(ExecutionGraph, ExecutionUnitLayout), ArchitecturePartitionError>
1739where
1740    B: eredu_nn::NeuralBackend,
1741    S: crate::RuntimeState<B>,
1742    M: crate::LayeredArchitecture<B, S>,
1743    M::Error: std::fmt::Display,
1744{
1745    let graph = architecture
1746        .execution_graph()
1747        .map_err(|error| ArchitecturePartitionError::ArchitectureTopology(error.to_string()))?;
1748    let primary = architecture.primary_execution_group();
1749    let primary_index = graph.group_index(primary).ok_or_else(|| {
1750        ArchitecturePartitionError::ArchitectureTopology(format!(
1751            "primary execution group {primary:?} is not present in the canonical graph"
1752        ))
1753    })?;
1754    let primary_transport = architecture.group_transport(primary_index);
1755    if primary_transport.kind != crate::ArchitectureGroupKind::Decoder
1756        || primary_transport.placement != crate::ArchitectureGroupPlacement::Pipeline
1757    {
1758        return Err(ArchitecturePartitionError::ArchitectureTopology(format!(
1759            "primary execution group {primary:?} must be a pipeline decoder"
1760        )));
1761    }
1762    let mut declared_groups = BTreeSet::from([primary.to_owned()]);
1763    for prediction in architecture.prediction_execution_groups() {
1764        let prediction_index = graph.group_index(&prediction).ok_or_else(|| {
1765            ArchitecturePartitionError::ArchitectureTopology(format!(
1766                "prediction execution group {prediction:?} is not present in the canonical graph"
1767            ))
1768        })?;
1769        let prediction_transport = architecture.group_transport(prediction_index);
1770        if prediction_transport.kind != crate::ArchitectureGroupKind::Prediction
1771            || prediction_transport.placement != crate::ArchitectureGroupPlacement::OutputOwner
1772        {
1773            return Err(ArchitecturePartitionError::ArchitectureTopology(format!(
1774                "prediction execution group {prediction:?} must be an output-owner prediction"
1775            )));
1776        }
1777        if !declared_groups.insert(prediction.clone()) {
1778            return Err(ArchitecturePartitionError::ArchitectureTopology(format!(
1779                "execution group {prediction:?} is declared as a primary or prediction group more than once"
1780            )));
1781        }
1782    }
1783    let mut counts = Vec::with_capacity(graph.groups().len());
1784    let mut paths = BTreeSet::new();
1785    for group in 0..graph.groups().len() {
1786        let count = architecture
1787            .group_unit_count(group)
1788            .map_err(|error| ArchitecturePartitionError::ArchitectureTopology(error.to_string()))?;
1789        counts.push(count);
1790        for index in 0..count {
1791            let path = architecture.unit_path(group, index).map_err(|error| {
1792                ArchitecturePartitionError::ArchitectureTopology(error.to_string())
1793            })?;
1794            if path.trim().is_empty() {
1795                return Err(ArchitecturePartitionError::EmptyArchitectureUnitPath { group, index });
1796            }
1797            if !paths.insert(path.clone()) {
1798                return Err(ArchitecturePartitionError::DuplicateArchitectureUnitPath(
1799                    path,
1800                ));
1801            }
1802        }
1803    }
1804    let unit_layout = ExecutionUnitLayout::new(&graph, counts)
1805        .map_err(|error| ArchitecturePartitionError::ArchitectureTopology(error.to_string()))?;
1806    Ok((graph, unit_layout))
1807}
1808
1809fn validate_canonical_layout(
1810    graph: &ExecutionGraph,
1811    layout: &ExecutionUnitLayout,
1812) -> Result<(), ArchitecturePartitionError> {
1813    if graph.groups().len() != layout.group_count() {
1814        return Err(ArchitecturePartitionError::LayoutGroupCountMismatch {
1815            graph: graph.groups().len(),
1816            layout: layout.group_count(),
1817        });
1818    }
1819    for (index, group) in graph.groups().iter().enumerate() {
1820        let layout_group = layout
1821            .group_id(index)
1822            .expect("matching group counts provide every layout identity");
1823        if layout_group.as_str() != group.id() {
1824            return Err(ArchitecturePartitionError::LayoutGroupMismatch {
1825                index,
1826                graph: group.id().to_owned(),
1827                layout: layout_group.as_str().to_owned(),
1828            });
1829        }
1830    }
1831    Ok(())
1832}
1833
1834/// Invalid backend-neutral architecture partition declaration.
1835#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
1836#[non_exhaustive]
1837pub enum ArchitecturePartitionError {
1838    /// The architecture supplied an invalid partition-boundary wire schema.
1839    #[error("invalid architecture partition boundary: {0}")]
1840    InvalidBoundary(#[from] ArchitectureBoundaryError),
1841    /// The neutral architecture could not declare a canonical graph, unit
1842    /// count, or unit path.
1843    #[error("neutral architecture topology is invalid: {0}")]
1844    ArchitectureTopology(String),
1845    /// The architecture could not declare or partition its mutable state.
1846    #[error("neutral architecture state is invalid: {0}")]
1847    ArchitectureState(String),
1848    /// The realized partition owns no mutable architecture state.
1849    #[error("architecture partition owns no mutable state")]
1850    MissingArchitectureState,
1851    /// The architecture state could not be converted to prompt-cache identity.
1852    #[error("architecture prompt-cache identity is invalid: {0}")]
1853    PromptCacheIdentity(String),
1854    /// A neutral architecture exposed an empty stable unit path.
1855    #[error("neutral architecture unit {group}:{index} has an empty path")]
1856    EmptyArchitectureUnitPath {
1857        /// Canonical execution-group slot.
1858        group: usize,
1859        /// Group-local unit index.
1860        index: usize,
1861    },
1862    /// Two canonical architecture units exposed the same stable path.
1863    #[error("neutral architecture repeats unit path {0:?}")]
1864    DuplicateArchitectureUnitPath(String),
1865    /// The partition dependency graph differs from the concrete architecture.
1866    #[error("architecture partition dependency graph differs from the neutral architecture")]
1867    ArchitectureGraphMismatch,
1868    /// The partition unit counts differ from the concrete architecture.
1869    #[error("architecture partition unit layout differs from the neutral architecture")]
1870    ArchitectureUnitLayoutMismatch,
1871    /// The graph and complete unit layout contain different group counts.
1872    #[error("execution graph contains {graph} groups but its unit layout contains {layout}")]
1873    LayoutGroupCountMismatch {
1874        /// Canonical graph group count.
1875        graph: usize,
1876        /// Unit-layout group count.
1877        layout: usize,
1878    },
1879    /// A unit-layout group identity differs from the graph at the same slot.
1880    #[error("execution group {index} is {graph:?} in the graph but {layout:?} in the unit layout")]
1881    LayoutGroupMismatch {
1882        /// Canonical group slot.
1883        index: usize,
1884        /// Graph identity.
1885        graph: String,
1886        /// Unit-layout identity.
1887        layout: String,
1888    },
1889    /// A rank-local unit range names no canonical architecture group.
1890    #[error("architecture partition names unknown execution group {0:?}")]
1891    UnknownGroup(String),
1892    /// A canonical architecture group was declared more than once.
1893    #[error("architecture partition repeats execution group {0:?}")]
1894    DuplicateGroup(String),
1895    /// A group owns no execution units.
1896    #[error("architecture partition declares an empty unit range for group {group:?}")]
1897    EmptyGroupRange {
1898        /// Canonical group identity.
1899        group: String,
1900    },
1901    /// A group-local global unit range exceeds the canonical group size.
1902    #[error(
1903        "architecture partition range {start}..{end} for group {group:?} exceeds {available} units"
1904    )]
1905    GroupRangeOutOfBounds {
1906        /// Canonical group identity.
1907        group: String,
1908        /// Invalid range start.
1909        start: usize,
1910        /// Invalid range end.
1911        end: usize,
1912        /// Canonical group unit count.
1913        available: usize,
1914    },
1915    /// A static ownership role is blank.
1916    #[error("architecture partition static role must not be empty")]
1917    EmptyStaticRole,
1918    /// A static ownership role was repeated.
1919    #[error("architecture partition repeats static role {0:?}")]
1920    DuplicateStaticRole(String),
1921    /// A local state layout cannot be placed in the global layer index space.
1922    #[error("state layer offset {offset} plus {layers} local layers overflowed usize")]
1923    StateOffsetOverflow {
1924        /// Requested global layer offset.
1925        offset: usize,
1926        /// Local state-layer count.
1927        layers: usize,
1928    },
1929    /// Two semantic parameter groups claim the same physical target.
1930    #[error("architecture partition repeats parameter target {0:?}")]
1931    DuplicateParameterTarget(String),
1932    /// A supplied parameter owner is not part of this rank-local partition.
1933    #[error("architecture partition includes non-local parameter owner {0:?}")]
1934    NonLocalParameterOwner(ParameterGroupOwner),
1935}
1936
1937#[cfg(test)]
1938mod tests {
1939    use super::*;
1940    use crate::{MemberSharding, ParameterMemberSpec, ParameterRole};
1941    use eredu_core::{cache::LayerCachePolicy, LayerSchedule};
1942
1943    #[derive(Debug, Clone, Eq, PartialEq)]
1944    struct Geometry(&'static str);
1945
1946    #[derive(Debug, Clone, Eq, PartialEq)]
1947    struct Boundary {
1948        route: usize,
1949    }
1950
1951    #[derive(Debug, Clone, Eq, PartialEq)]
1952    struct PairBoundary<T> {
1953        tokens: T,
1954        embedded: T,
1955    }
1956
1957    #[derive(Debug, Clone, Copy)]
1958    struct PairBoundarySchema;
1959
1960    impl ArchitectureBoundary for PairBoundarySchema {
1961        type Boundary<T> = PairBoundary<T>;
1962
1963        const IDENTITY: &'static str = "fixture.target";
1964
1965        fn primary_tensor_spec(&self) -> BoundaryTensorSpec {
1966            BoundaryTensorSpec::primary_activation(8)
1967        }
1968
1969        fn auxiliary_tensor_specs(&self) -> Vec<BoundaryTensorSpec> {
1970            vec![
1971                BoundaryTensorSpec::new(
1972                    "tokens",
1973                    [
1974                        BoundaryTensorDimension::Batch,
1975                        BoundaryTensorDimension::Sequence,
1976                    ],
1977                    BoundaryTensorDtype::Uint32,
1978                ),
1979                BoundaryTensorSpec::new(
1980                    "embedded",
1981                    [
1982                        BoundaryTensorDimension::Batch,
1983                        BoundaryTensorDimension::Sequence,
1984                        BoundaryTensorDimension::Fixed(16),
1985                    ],
1986                    BoundaryTensorDtype::Activation,
1987                ),
1988            ]
1989        }
1990
1991        fn encode<T>(
1992            &self,
1993            boundary: Self::Boundary<T>,
1994        ) -> Result<Vec<ArchitectureBoundaryValue<T>>, ArchitectureBoundaryError> {
1995            Ok(vec![
1996                ArchitectureBoundaryValue::new("tokens", boundary.tokens)?,
1997                ArchitectureBoundaryValue::new("embedded", boundary.embedded)?,
1998            ])
1999        }
2000
2001        fn decode<T>(
2002            &self,
2003            mut tensors: Vec<T>,
2004        ) -> Result<Self::Boundary<T>, ArchitectureBoundaryError> {
2005            validate_boundary_tensor_count(self, &tensors)?;
2006            let embedded = tensors.pop().expect("validated embedded tensor");
2007            let tokens = tensors.pop().expect("validated token tensor");
2008            Ok(PairBoundary { tokens, embedded })
2009        }
2010    }
2011
2012    fn graph() -> ExecutionGraph {
2013        ExecutionGraph::chain(["primary", "prediction"]).unwrap()
2014    }
2015
2016    fn layout(graph: &ExecutionGraph) -> ExecutionUnitLayout {
2017        ExecutionUnitLayout::new(graph, [4, 3]).unwrap()
2018    }
2019
2020    fn state_layout(layers: usize) -> StateLayout {
2021        StateLayout::new(
2022            LayerSchedule::new(layers, vec![LayerCachePolicy::NoState; layers]).unwrap(),
2023        )
2024        .unwrap()
2025    }
2026
2027    fn parameter(logical: &str, target: &str) -> ParameterGroupSpec {
2028        ParameterGroupSpec::new(
2029            logical,
2030            ParameterRole::Replicated,
2031            [ParameterMemberSpec::new(
2032                target,
2033                vec![2, 2],
2034                MemberSharding::Replicated,
2035            )],
2036        )
2037        .unwrap()
2038    }
2039
2040    fn valid_partition() -> ArchitecturePartition<Geometry, Boundary> {
2041        let graph = graph();
2042        let layout = layout(&graph);
2043        ArchitecturePartition::new(
2044            graph,
2045            layout,
2046            [("prediction", 0..2), ("primary", 1..4)],
2047            PartitionOwnership::new(true, false, ["embedding", "normalization"]).unwrap(),
2048            Some(PartitionState::new(state_layout(2), 7).unwrap()),
2049            Geometry("local"),
2050            Boundary { route: 3 },
2051            [
2052                OwnedParameterGroupSpec::new(
2053                    ParameterGroupOwner::static_role("embedding"),
2054                    parameter("model.embed_tokens", "model.embed_tokens.weight"),
2055                ),
2056                OwnedParameterGroupSpec::new(
2057                    ParameterGroupOwner::execution_unit(
2058                        ExecutionGroupId::new("primary").unwrap(),
2059                        1,
2060                    ),
2061                    parameter("model.layers.1", "model.layers.1.weight"),
2062                ),
2063            ],
2064        )
2065        .unwrap()
2066    }
2067
2068    fn state_plan_partition(
2069        primary: Range<usize>,
2070        ownership: PartitionOwnership,
2071    ) -> ArchitecturePartition<(), ()> {
2072        let graph = graph();
2073        ArchitecturePartition::new(
2074            graph.clone(),
2075            layout(&graph),
2076            [("primary", primary)],
2077            ownership,
2078            None,
2079            (),
2080            (),
2081            [],
2082        )
2083        .unwrap()
2084    }
2085
2086    #[test]
2087    fn architecture_state_plan_attaches_declared_tail_to_output_owner() {
2088        let complete = state_layout(6);
2089        let plan = ArchitectureStatePartitionPlan::new([
2090            crate::ArchitectureStatePartitionRule::group_units(0, 0..4),
2091            crate::ArchitectureStatePartitionRule::output_owner(4..6),
2092        ]);
2093        let interior = state_plan_partition(
2094            1..3,
2095            PartitionOwnership::new(false, false, std::iter::empty::<&str>()).unwrap(),
2096        );
2097        let output = state_plan_partition(
2098            3..4,
2099            PartitionOwnership::new(false, true, std::iter::empty::<&str>()).unwrap(),
2100        );
2101
2102        assert_eq!(
2103            interior
2104                .resolve_state_partition(&complete, &plan)
2105                .unwrap()
2106                .unwrap()
2107                .global_layers(),
2108            1..3
2109        );
2110        assert_eq!(
2111            output
2112                .resolve_state_partition(&complete, &plan)
2113                .unwrap()
2114                .unwrap()
2115                .global_layers(),
2116            3..6
2117        );
2118    }
2119
2120    #[test]
2121    fn architecture_state_plan_rejects_noncontiguous_local_state() {
2122        let complete = state_layout(6);
2123        let plan = ArchitectureStatePartitionPlan::new([
2124            crate::ArchitectureStatePartitionRule::output_owner(0..2),
2125            crate::ArchitectureStatePartitionRule::group_units(0, 2..6),
2126        ]);
2127        let output = state_plan_partition(
2128            3..4,
2129            PartitionOwnership::new(false, true, std::iter::empty::<&str>()).unwrap(),
2130        );
2131
2132        assert_eq!(
2133            output.resolve_state_partition(&complete, &plan),
2134            Err(ArchitectureStatePartitionError::DiscontiguousSelection {
2135                frontier: 2,
2136                start: 5,
2137            })
2138        );
2139    }
2140
2141    fn parameter_description(
2142        expected: Vec<ParameterGroupSpec>,
2143        groups: Vec<OwnedParameterGroupSpec>,
2144    ) -> Result<ArchitectureParameterDescription, ArchitectureParameterError> {
2145        let graph = graph();
2146        ArchitectureParameterDescription::new(&graph, &layout(&graph), expected, groups)
2147    }
2148
2149    #[test]
2150    fn description_driven_partition_selects_state_and_parameters_before_construction() {
2151        let embedding = parameter("embedding", "model.embed_tokens.weight");
2152        let layer = parameter("layer", "model.layers.1.weight");
2153        let description = parameter_description(
2154            vec![embedding.clone(), layer.clone()],
2155            vec![
2156                OwnedParameterGroupSpec::new(
2157                    ParameterGroupOwner::static_role("embedding"),
2158                    embedding,
2159                ),
2160                OwnedParameterGroupSpec::new(
2161                    ParameterGroupOwner::execution_unit(
2162                        ExecutionGroupId::new("primary").unwrap(),
2163                        1,
2164                    ),
2165                    layer,
2166                ),
2167            ],
2168        )
2169        .unwrap();
2170        let ownership = PartitionOwnership::new(true, false, ["embedding"]).unwrap();
2171        let state = state_layout(4);
2172        let state_plan = ArchitectureStatePartitionPlan::new([
2173            crate::ArchitectureStatePartitionRule::group_units(0, 0..4),
2174        ]);
2175
2176        let partition = ArchitecturePartition::from_description(
2177            &description,
2178            [("primary", 1..3)],
2179            ownership,
2180            &state,
2181            &state_plan,
2182            Geometry("selected-before-allocation"),
2183            PairBoundarySchema,
2184        )
2185        .unwrap();
2186
2187        assert_eq!(partition.groups()[0].global_units(), 1..3);
2188        assert_eq!(partition.state().unwrap().global_layers(), 1..3);
2189        assert_eq!(
2190            partition.local_geometry(),
2191            &Geometry("selected-before-allocation")
2192        );
2193        assert_eq!(partition.parameter_bindings().len(), 2);
2194        assert_eq!(
2195            partition
2196                .parameter_bindings()
2197                .iter()
2198                .flat_map(|group| group.members())
2199                .map(ParameterMemberSpec::target)
2200                .collect::<Vec<_>>(),
2201            ["model.embed_tokens.weight", "model.layers.1.weight"]
2202        );
2203    }
2204
2205    #[test]
2206    fn parameter_description_selects_static_roles_and_canonical_units() {
2207        let embedding = parameter("embedding", "model.embed_tokens.weight");
2208        let layer = parameter("layer", "model.layers.1.weight");
2209        let description = parameter_description(
2210            vec![embedding.clone(), layer.clone()],
2211            vec![
2212                OwnedParameterGroupSpec::new(
2213                    ParameterGroupOwner::static_role("embedding"),
2214                    embedding,
2215                ),
2216                OwnedParameterGroupSpec::new(
2217                    ParameterGroupOwner::execution_unit(
2218                        ExecutionGroupId::new("primary").unwrap(),
2219                        1,
2220                    ),
2221                    layer,
2222                ),
2223            ],
2224        )
2225        .unwrap();
2226        let partition = valid_partition();
2227        assert_eq!(description.graph(), partition.graph());
2228        assert_eq!(description.unit_layout(), partition.unit_layout());
2229        let selected = description.select_owned(&partition);
2230        assert_eq!(selected.len(), 2);
2231        assert_eq!(selected[0].logical_name(), "embedding");
2232        assert_eq!(selected[1].logical_name(), "layer");
2233        assert_eq!(
2234            selected[0].owner(),
2235            &ParameterGroupOwner::static_role("embedding")
2236        );
2237        assert_eq!(
2238            selected[1].owner(),
2239            &ParameterGroupOwner::execution_unit(ExecutionGroupId::new("primary").unwrap(), 1,)
2240        );
2241    }
2242
2243    #[test]
2244    fn parameter_description_selects_every_owned_target_for_a_role() {
2245        let expert = ParameterGroupSpec::new(
2246            "model.layers.1.expert_intermediate",
2247            ParameterRole::ExpertIntermediate,
2248            [
2249                ParameterMemberSpec::new(
2250                    "model.layers.1.moe.packed.weight",
2251                    vec![4, 2],
2252                    MemberSharding::Replicated,
2253                ),
2254                ParameterMemberSpec::new(
2255                    "model.layers.1.moe.packed.scales",
2256                    vec![4, 1],
2257                    MemberSharding::Replicated,
2258                ),
2259                ParameterMemberSpec::new(
2260                    "model.layers.1.moe.alias.biases",
2261                    vec![4, 1],
2262                    MemberSharding::Replicated,
2263                ),
2264            ],
2265        )
2266        .unwrap();
2267        let replicated = parameter("router", "model.layers.1.moe.router.weight");
2268        let owner =
2269            ParameterGroupOwner::execution_unit(ExecutionGroupId::new("primary").unwrap(), 1);
2270        let description = parameter_description(
2271            vec![expert.clone(), replicated.clone()],
2272            vec![
2273                OwnedParameterGroupSpec::new(owner.clone(), expert),
2274                OwnedParameterGroupSpec::new(owner, replicated),
2275            ],
2276        )
2277        .unwrap();
2278
2279        assert_eq!(
2280            description.targets_for_role(ParameterRole::ExpertIntermediate),
2281            BTreeSet::from([
2282                "model.layers.1.moe.alias.biases".to_owned(),
2283                "model.layers.1.moe.packed.scales".to_owned(),
2284                "model.layers.1.moe.packed.weight".to_owned(),
2285            ])
2286        );
2287    }
2288
2289    #[test]
2290    fn parameter_description_selects_shared_static_owner_by_any_consumer() {
2291        let embedding = parameter("embedding", "model.embed_tokens.weight");
2292        let description = parameter_description(
2293            vec![embedding.clone()],
2294            vec![OwnedParameterGroupSpec::new(
2295                ParameterGroupOwner::static_any_of(["output", "embedding"]),
2296                embedding,
2297            )],
2298        )
2299        .unwrap();
2300        assert_eq!(description.select_owned(&valid_partition()).len(), 1);
2301
2302        let duplicate = parameter("embedding", "model.embed_tokens.weight");
2303        assert_eq!(
2304            parameter_description(
2305                vec![duplicate.clone()],
2306                vec![OwnedParameterGroupSpec::new(
2307                    ParameterGroupOwner::static_any_of(["embedding", "embedding"]),
2308                    duplicate,
2309                )],
2310            )
2311            .unwrap_err(),
2312            ArchitectureParameterError::DuplicateStaticRole,
2313        );
2314    }
2315
2316    #[test]
2317    fn partition_rejects_parameter_owner_outside_local_unit_ranges() {
2318        let graph = graph();
2319        let error = ArchitecturePartition::new(
2320            graph.clone(),
2321            layout(&graph),
2322            [("primary", 1..4)],
2323            PartitionOwnership::new(false, false, ["embedding"]).unwrap(),
2324            None,
2325            (),
2326            (),
2327            [OwnedParameterGroupSpec::new(
2328                ParameterGroupOwner::execution_unit(
2329                    ExecutionGroupId::new("prediction").unwrap(),
2330                    0,
2331                ),
2332                parameter("prediction", "prediction.weight"),
2333            )],
2334        )
2335        .unwrap_err();
2336        assert!(matches!(
2337            error,
2338            ArchitecturePartitionError::NonLocalParameterOwner(
2339                ParameterGroupOwner::ExecutionUnit { .. }
2340            )
2341        ));
2342    }
2343
2344    #[test]
2345    fn parameter_description_rejects_missing_duplicate_and_out_of_range_ownership() {
2346        let embedding = parameter("embedding", "model.embed_tokens.weight");
2347        let layer = parameter("layer", "model.layers.1.weight");
2348        assert_eq!(
2349            parameter_description(
2350                vec![embedding.clone(), layer.clone()],
2351                vec![OwnedParameterGroupSpec::new(
2352                    ParameterGroupOwner::static_role("embedding"),
2353                    embedding.clone(),
2354                )],
2355            )
2356            .unwrap_err(),
2357            ArchitectureParameterError::MissingOwnership("model.layers.1.weight".into())
2358        );
2359        assert!(matches!(
2360            parameter_description(
2361                vec![embedding.clone()],
2362                vec![
2363                    OwnedParameterGroupSpec::new(
2364                        ParameterGroupOwner::static_role("embedding"),
2365                        embedding.clone(),
2366                    ),
2367                    OwnedParameterGroupSpec::new(
2368                        ParameterGroupOwner::static_role("output"),
2369                        embedding.clone(),
2370                    ),
2371                ],
2372            )
2373            .unwrap_err(),
2374            ArchitectureParameterError::DuplicateOwnership { .. }
2375        ));
2376        assert_eq!(
2377            parameter_description(
2378                vec![layer.clone()],
2379                vec![OwnedParameterGroupSpec::new(
2380                    ParameterGroupOwner::execution_unit(
2381                        ExecutionGroupId::new("prediction").unwrap(),
2382                        3,
2383                    ),
2384                    layer,
2385                )],
2386            )
2387            .unwrap_err(),
2388            ArchitectureParameterError::UnitOutOfRange {
2389                group: "prediction".into(),
2390                global_unit: 3,
2391                available: 3,
2392            }
2393        );
2394    }
2395
2396    #[test]
2397    fn retains_canonical_topology_ownership_and_typed_family_values() {
2398        let mut partition = valid_partition();
2399        assert_eq!(partition.graph().groups().len(), 2);
2400        assert_eq!(partition.unit_layout().len(), 7);
2401        assert_eq!(partition.groups()[0].group().as_str(), "primary");
2402        assert_eq!(partition.groups()[0].group_index(), 0);
2403        assert_eq!(partition.groups()[0].global_units(), 1..4);
2404        assert!(partition.owns_unit("primary", 3));
2405        assert!(!partition.owns_unit("primary", 0));
2406        assert!(partition.ownership().owns_input());
2407        assert!(!partition.ownership().owns_output());
2408        assert!(partition.ownership().owns_static_role("embedding"));
2409        assert_eq!(
2410            partition
2411                .units()
2412                .map(|unit| (unit.group(), unit.index()))
2413                .collect::<Vec<_>>(),
2414            [(0, 1), (0, 2), (0, 3), (1, 0), (1, 1)]
2415        );
2416        assert_eq!(partition.state().unwrap().global_layers(), 7..9);
2417        assert_eq!(partition.local_geometry(), &Geometry("local"));
2418        partition.boundary_schema_mut().route = 5;
2419        assert_eq!(partition.boundary_schema().route, 5);
2420        assert_eq!(partition.parameter_bindings().len(), 2);
2421    }
2422
2423    #[test]
2424    fn typed_boundary_owns_roles_order_and_atomic_cardinality_validation() {
2425        let boundary = PairBoundary {
2426            tokens: 3,
2427            embedded: 7,
2428        };
2429        let schema = PairBoundarySchema;
2430        let values = schema.encode(boundary).unwrap();
2431        assert_eq!(values[0].role(), "tokens");
2432        assert_eq!(values[1].role(), "embedded");
2433        let tensors = values
2434            .into_iter()
2435            .map(ArchitectureBoundaryValue::into_parts)
2436            .map(|(_, tensor)| tensor)
2437            .collect();
2438        assert_eq!(
2439            schema.decode(tensors).unwrap(),
2440            PairBoundary {
2441                tokens: 3,
2442                embedded: 7
2443            }
2444        );
2445        let resolved = schema.wire_schema().unwrap().resolve(2, 3).unwrap();
2446        assert_eq!(resolved.primary().shape(), [2, 3, 8]);
2447        assert_eq!(resolved.primary().dtype(), BoundaryTensorDtype::Activation);
2448        assert_eq!(resolved.auxiliary()[0].shape(), [2, 3]);
2449        assert_eq!(resolved.auxiliary()[0].dtype(), BoundaryTensorDtype::Uint32);
2450        assert_eq!(resolved.auxiliary()[1].shape(), [2, 3, 16]);
2451        assert_eq!(
2452            resolved.auxiliary()[1].dtype(),
2453            BoundaryTensorDtype::Activation
2454        );
2455        assert_eq!(
2456            schema.decode(vec![3]).unwrap_err(),
2457            ArchitectureBoundaryError::TensorCount {
2458                boundary: "fixture.target",
2459                expected: 2,
2460                actual: 1,
2461            }
2462        );
2463    }
2464
2465    #[test]
2466    fn boundary_schema_rejects_role_and_geometry_drift_before_transport() {
2467        let invalid_primary = BoundaryWireSchema::new(
2468            "fixture.invalid",
2469            BoundaryTensorSpec::new(
2470                "hidden",
2471                [BoundaryTensorDimension::Fixed(8)],
2472                BoundaryTensorDtype::Uint32,
2473            ),
2474            [],
2475        )
2476        .unwrap_err();
2477        assert_eq!(
2478            invalid_primary,
2479            ArchitectureBoundaryError::InvalidPrimaryDtype {
2480                boundary: "fixture.invalid",
2481            }
2482        );
2483
2484        let duplicate = BoundaryWireSchema::new(
2485            "fixture.invalid",
2486            BoundaryTensorSpec::primary_activation(8),
2487            [
2488                BoundaryTensorSpec::new(
2489                    "state",
2490                    [BoundaryTensorDimension::Fixed(1)],
2491                    BoundaryTensorDtype::Activation,
2492                ),
2493                BoundaryTensorSpec::new(
2494                    "state",
2495                    [BoundaryTensorDimension::Fixed(2)],
2496                    BoundaryTensorDtype::Activation,
2497                ),
2498            ],
2499        )
2500        .unwrap_err();
2501        assert_eq!(
2502            duplicate,
2503            ArchitectureBoundaryError::DuplicateTensorRole {
2504                boundary: "fixture.invalid",
2505                role: "state".into(),
2506            }
2507        );
2508
2509        let invalid = BoundaryWireSchema::new(
2510            "fixture.invalid",
2511            BoundaryTensorSpec::primary_activation(8),
2512            [BoundaryTensorSpec::new(
2513                "state",
2514                [BoundaryTensorDimension::Fixed(0)],
2515                BoundaryTensorDtype::Activation,
2516            )],
2517        )
2518        .unwrap_err();
2519        assert_eq!(
2520            invalid,
2521            ArchitectureBoundaryError::InvalidTensorDimension {
2522                boundary: "fixture.invalid",
2523                role: "state".into(),
2524            }
2525        );
2526    }
2527
2528    #[test]
2529    fn rejects_noncanonical_unknown_and_duplicate_groups() {
2530        let graph = graph();
2531        let mismatched_graph = ExecutionGraph::chain(["primary", "other"]).unwrap();
2532        let error = ArchitecturePartition::new(
2533            graph.clone(),
2534            layout(&mismatched_graph),
2535            [("primary", 0..1)],
2536            PartitionOwnership::new(false, false, std::iter::empty::<String>()).unwrap(),
2537            None,
2538            (),
2539            (),
2540            std::iter::empty(),
2541        )
2542        .unwrap_err();
2543        assert!(matches!(
2544            error,
2545            ArchitecturePartitionError::LayoutGroupMismatch { .. }
2546        ));
2547
2548        let error = ArchitecturePartition::new(
2549            graph.clone(),
2550            layout(&graph),
2551            [("missing", 0..1)],
2552            PartitionOwnership::new(false, false, std::iter::empty::<String>()).unwrap(),
2553            None,
2554            (),
2555            (),
2556            std::iter::empty(),
2557        )
2558        .unwrap_err();
2559        assert_eq!(
2560            error,
2561            ArchitecturePartitionError::UnknownGroup("missing".into())
2562        );
2563
2564        let error = ArchitecturePartition::new(
2565            graph.clone(),
2566            layout(&graph),
2567            [("primary", 0..1), ("primary", 1..2)],
2568            PartitionOwnership::new(false, false, std::iter::empty::<String>()).unwrap(),
2569            None,
2570            (),
2571            (),
2572            std::iter::empty(),
2573        )
2574        .unwrap_err();
2575        assert_eq!(
2576            error,
2577            ArchitecturePartitionError::DuplicateGroup("primary".into())
2578        );
2579    }
2580
2581    #[test]
2582    fn rejects_empty_and_out_of_bounds_group_ranges() {
2583        let graph = graph();
2584        let error = ArchitecturePartition::new(
2585            graph.clone(),
2586            layout(&graph),
2587            [("primary", 2..2)],
2588            PartitionOwnership::new(false, false, std::iter::empty::<String>()).unwrap(),
2589            None,
2590            (),
2591            (),
2592            std::iter::empty(),
2593        )
2594        .unwrap_err();
2595        assert!(matches!(
2596            error,
2597            ArchitecturePartitionError::EmptyGroupRange { .. }
2598        ));
2599
2600        let error = ArchitecturePartition::new(
2601            graph.clone(),
2602            layout(&graph),
2603            [("prediction", 1..4)],
2604            PartitionOwnership::new(false, false, std::iter::empty::<String>()).unwrap(),
2605            None,
2606            (),
2607            (),
2608            std::iter::empty(),
2609        )
2610        .unwrap_err();
2611        assert!(matches!(
2612            error,
2613            ArchitecturePartitionError::GroupRangeOutOfBounds { .. }
2614        ));
2615    }
2616
2617    #[test]
2618    fn rejects_state_offset_overflow() {
2619        assert_eq!(
2620            PartitionState::new(state_layout(2), usize::MAX).unwrap_err(),
2621            ArchitecturePartitionError::StateOffsetOverflow {
2622                offset: usize::MAX,
2623                layers: 2,
2624            }
2625        );
2626    }
2627
2628    #[test]
2629    fn rejects_empty_static_roles_and_duplicate_parameter_targets() {
2630        assert_eq!(
2631            PartitionOwnership::new(false, false, [" "]).unwrap_err(),
2632            ArchitecturePartitionError::EmptyStaticRole
2633        );
2634
2635        let graph = graph();
2636        let error = ArchitecturePartition::new(
2637            graph.clone(),
2638            layout(&graph),
2639            [("primary", 0..1)],
2640            PartitionOwnership::new(false, false, ["embedding", "normalization"]).unwrap(),
2641            None,
2642            (),
2643            (),
2644            [
2645                OwnedParameterGroupSpec::new(
2646                    ParameterGroupOwner::static_role("embedding"),
2647                    parameter("first", "shared.weight"),
2648                ),
2649                OwnedParameterGroupSpec::new(
2650                    ParameterGroupOwner::static_role("normalization"),
2651                    parameter("second", "shared.weight"),
2652                ),
2653            ],
2654        )
2655        .unwrap_err();
2656        assert_eq!(
2657            error,
2658            ArchitecturePartitionError::DuplicateParameterTarget("shared.weight".into())
2659        );
2660    }
2661
2662    fn layered_partition(
2663        storage_state: Range<usize>,
2664        owns_input: bool,
2665    ) -> ArchitecturePartition<(), ()> {
2666        let graph = ExecutionGraph::chain(["decoder"]).unwrap();
2667        let layout = ExecutionUnitLayout::new(&graph, [4]).unwrap();
2668        ArchitecturePartition::new(
2669            graph,
2670            layout,
2671            [("decoder", 1..3)],
2672            PartitionOwnership::new(owns_input, false, std::iter::empty::<String>()).unwrap(),
2673            Some(
2674                PartitionState::new(state_layout(storage_state.len()), storage_state.start)
2675                    .unwrap(),
2676            ),
2677            (),
2678            (),
2679            std::iter::empty(),
2680        )
2681        .unwrap()
2682    }
2683
2684    #[test]
2685    fn layered_driver_rejects_storage_and_state_range_drift() {
2686        let partition = layered_partition(1..3, true);
2687        assert!(LayeredPartitionDriver::new(&partition, 0, 1..3).is_ok());
2688        assert_eq!(
2689            LayeredPartitionDriver::new(&partition, 0, 0..2).unwrap_err(),
2690            LayeredPartitionError::StorageRange {
2691                storage: 0..2,
2692                partition: 1..3,
2693            }
2694        );
2695
2696        let partition = layered_partition(0..2, true);
2697        assert_eq!(
2698            LayeredPartitionDriver::new(&partition, 0, 1..3).unwrap_err(),
2699            LayeredPartitionError::StateRange {
2700                state: 0..2,
2701                partition: 1..3,
2702            }
2703        );
2704    }
2705
2706    #[test]
2707    fn layered_driver_represents_stateless_root_without_borrowing_decoder_state() {
2708        let graph = ExecutionGraph::chain(["vision", "decoder"]).unwrap();
2709        let layout = ExecutionUnitLayout::new(&graph, [1, 2]).unwrap();
2710        let partition = ArchitecturePartition::new(
2711            graph,
2712            layout,
2713            [("vision", 0..1), ("decoder", 0..2)],
2714            PartitionOwnership::new(true, true, std::iter::empty::<String>()).unwrap(),
2715            Some(PartitionState::new(state_layout(1), 1).unwrap()),
2716            (),
2717            (),
2718            std::iter::empty(),
2719        )
2720        .unwrap();
2721
2722        let vision =
2723            LayeredPartitionDriver::new_with_state_ownership(&partition, 0, 0..1, false).unwrap();
2724        assert!(vision.optional_state_layout().is_none());
2725        assert_eq!(vision.group_index(), 0);
2726
2727        let without_state = ArchitecturePartition::new(
2728            ExecutionGraph::chain(["vision"]).unwrap(),
2729            ExecutionUnitLayout::new(&ExecutionGraph::chain(["vision"]).unwrap(), [1]).unwrap(),
2730            [("vision", 0..1)],
2731            PartitionOwnership::new(true, false, std::iter::empty::<String>()).unwrap(),
2732            None,
2733            (),
2734            (),
2735            std::iter::empty(),
2736        )
2737        .unwrap();
2738        assert_eq!(
2739            LayeredPartitionDriver::new(&without_state, 0, 0..1).unwrap_err(),
2740            LayeredPartitionError::MissingState
2741        );
2742        assert!(
2743            LayeredPartitionDriver::new_with_state_ownership(&without_state, 0, 0..1, false)
2744                .unwrap()
2745                .optional_state_layout()
2746                .is_none()
2747        );
2748    }
2749
2750    #[test]
2751    fn layered_driver_restricts_tokens_but_accepts_architecture_prepared_hidden() {
2752        let input_owner =
2753            LayeredPartitionDriver::new(&layered_partition(1..3, true), 0, 1..3).unwrap();
2754        assert!(matches!(
2755            input_owner.input(LayeredPartitionInput::<i32, NoAuxiliaryBoundary>::Tokens(
2756                &7
2757            )),
2758            Ok(LayeredPartitionInput::Tokens(7))
2759        ));
2760        assert!(matches!(
2761            input_owner.input(LayeredPartitionInput::Hidden {
2762                hidden: 7,
2763                auxiliary: NoAuxiliaryBoundary,
2764            }),
2765            Ok(LayeredPartitionInput::Hidden {
2766                hidden: 7,
2767                auxiliary: NoAuxiliaryBoundary,
2768            })
2769        ));
2770
2771        let hidden_owner =
2772            LayeredPartitionDriver::new(&layered_partition(1..3, false), 0, 1..3).unwrap();
2773        assert_eq!(
2774            hidden_owner
2775                .input(LayeredPartitionInput::<i32, NoAuxiliaryBoundary>::Tokens(
2776                    &7
2777                ))
2778                .unwrap_err(),
2779            LayeredPartitionError::TokensOnNonInputOwner
2780        );
2781        assert!(matches!(
2782            hidden_owner.input(LayeredPartitionInput::Hidden {
2783                hidden: 7,
2784                auxiliary: NoAuxiliaryBoundary,
2785            }),
2786            Ok(LayeredPartitionInput::Hidden {
2787                hidden: 7,
2788                auxiliary: NoAuxiliaryBoundary,
2789            })
2790        ));
2791    }
2792}