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        if batch_size <= 0 || sequence_length <= 0 {
579            return Err(ArchitectureBoundaryError::InvalidInvocationGeometry {
580                boundary: self.identity,
581                batch_size,
582                sequence_length,
583            });
584        }
585        let resolve = |tensor: &BoundaryTensorSpec| ResolvedBoundaryTensorSpec {
586            role: tensor.role.clone(),
587            shape: tensor
588                .shape
589                .iter()
590                .map(|dimension| match dimension {
591                    BoundaryTensorDimension::Batch => batch_size,
592                    BoundaryTensorDimension::Sequence => sequence_length,
593                    BoundaryTensorDimension::Fixed(value) => *value,
594                })
595                .collect(),
596            dtype: tensor.dtype,
597        };
598        Ok(ResolvedBoundaryWireSchema {
599            identity: self.identity,
600            primary: resolve(&self.primary),
601            auxiliary: self.auxiliary.iter().map(resolve).collect(),
602        })
603    }
604}
605
606/// One architecture boundary after invocation-dependent dimensions are resolved.
607#[derive(Debug, Clone, Eq, Hash, PartialEq)]
608pub struct ResolvedBoundaryWireSchema {
609    identity: &'static str,
610    primary: ResolvedBoundaryTensorSpec,
611    auxiliary: Vec<ResolvedBoundaryTensorSpec>,
612}
613
614impl ResolvedBoundaryWireSchema {
615    /// Returns the stable schema identity.
616    pub const fn identity(&self) -> &'static str {
617        self.identity
618    }
619
620    /// Returns the resolved primary evolving activation declaration.
621    pub const fn primary(&self) -> &ResolvedBoundaryTensorSpec {
622        &self.primary
623    }
624
625    /// Returns resolved auxiliary declarations in canonical transport order.
626    pub fn auxiliary(&self) -> &[ResolvedBoundaryTensorSpec] {
627        &self.auxiliary
628    }
629}
630
631/// Typed architecture-owned tensor state and wire geometry carried across one
632/// partition boundary.
633///
634/// The runtime and a backend transport may resolve and move the encoded tensor
635/// vector, but only this family schema assigns semantic roles, shape, dtype,
636/// cardinality, or reconstructs the typed value.
637pub trait ArchitectureBoundary: Sized {
638    /// Typed family value transported by this schema.
639    type Boundary<T>;
640
641    /// Stable non-empty semantic identity used in diagnostics and wire schema
642    /// validation.
643    const IDENTITY: &'static str;
644
645    /// Primary evolving activation declaration.
646    fn primary_tensor_spec(&self) -> BoundaryTensorSpec;
647
648    /// Auxiliary tensor declarations in exact encoded order.
649    fn auxiliary_tensor_specs(&self) -> Vec<BoundaryTensorSpec>;
650
651    /// Consumes this typed value into transport-order tensors.
652    fn encode<T>(&self, boundary: Self::Boundary<T>) -> Result<Vec<T>, ArchitectureBoundaryError>;
653
654    /// Reconstructs the typed value from transport-order tensors.
655    fn decode<T>(&self, tensors: Vec<T>) -> Result<Self::Boundary<T>, ArchitectureBoundaryError>;
656
657    /// Returns the validated backend-neutral wire schema.
658    fn wire_schema(&self) -> Result<BoundaryWireSchema, ArchitectureBoundaryError> {
659        BoundaryWireSchema::new(
660            Self::IDENTITY,
661            self.primary_tensor_spec(),
662            self.auxiliary_tensor_specs(),
663        )
664    }
665}
666
667/// Explicit declaration that an architecture partition carries no auxiliary
668/// tensors across its boundary.
669///
670/// This marker is preferable to `()` because it still participates in the
671/// typed boundary contract and rejects any unexpected transported tensor.
672#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
673pub struct NoAuxiliaryBoundary;
674
675/// Schema for an evolving decoder activation with no auxiliary tensors.
676#[derive(Debug, Clone, Copy, Eq, PartialEq)]
677pub struct NoAuxiliaryBoundarySchema {
678    hidden_size: i32,
679}
680
681impl NoAuxiliaryBoundarySchema {
682    /// Declares a standard batch/sequence/hidden activation boundary.
683    pub const fn new(hidden_size: i32) -> Self {
684        Self { hidden_size }
685    }
686}
687
688impl ArchitectureBoundary for NoAuxiliaryBoundarySchema {
689    type Boundary<T> = NoAuxiliaryBoundary;
690
691    const IDENTITY: &'static str = "none";
692
693    fn primary_tensor_spec(&self) -> BoundaryTensorSpec {
694        BoundaryTensorSpec::primary_activation(self.hidden_size)
695    }
696
697    fn auxiliary_tensor_specs(&self) -> Vec<BoundaryTensorSpec> {
698        Vec::new()
699    }
700
701    fn encode<T>(&self, _boundary: Self::Boundary<T>) -> Result<Vec<T>, ArchitectureBoundaryError> {
702        Ok(Vec::new())
703    }
704
705    fn decode<T>(&self, tensors: Vec<T>) -> Result<Self::Boundary<T>, ArchitectureBoundaryError> {
706        validate_boundary_tensor_count(self, &tensors)?;
707        Ok(NoAuxiliaryBoundary)
708    }
709}
710
711/// Validates the number of tensors before a family boundary decodes any
712/// positional value.
713pub fn validate_boundary_tensor_count<B, T>(
714    boundary: &B,
715    tensors: &[T],
716) -> Result<(), ArchitectureBoundaryError>
717where
718    B: ArchitectureBoundary,
719{
720    let expected = boundary.wire_schema()?.auxiliary().len();
721    let actual = tensors.len();
722    if actual != expected {
723        return Err(ArchitectureBoundaryError::TensorCount {
724            boundary: B::IDENTITY,
725            expected,
726            actual,
727        });
728    }
729    Ok(())
730}
731
732/// Invalid architecture-owned partition boundary declaration or payload.
733#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
734#[non_exhaustive]
735pub enum ArchitectureBoundaryError {
736    /// A family boundary omitted its stable identity.
737    #[error("architecture boundary identity must not be empty")]
738    EmptyIdentity,
739    /// A family boundary assigned a non-activation dtype to its primary tensor.
740    #[error("architecture boundary {boundary:?} primary tensor must use activation dtype")]
741    InvalidPrimaryDtype {
742        /// Stable boundary identity.
743        boundary: &'static str,
744    },
745    /// A family boundary declared an empty tensor role.
746    #[error("architecture boundary {boundary:?} contains an empty tensor role")]
747    EmptyTensorRole {
748        /// Stable boundary identity.
749        boundary: &'static str,
750    },
751    /// A family boundary declared one tensor role more than once.
752    #[error("architecture boundary {boundary:?} repeats tensor role {role:?}")]
753    DuplicateTensorRole {
754        /// Stable boundary identity.
755        boundary: &'static str,
756        /// Repeated semantic tensor role.
757        role: String,
758    },
759    /// A family boundary declared a rank-zero tensor.
760    #[error("architecture boundary {boundary:?} tensor {role:?} has no dimensions")]
761    EmptyTensorShape {
762        /// Stable boundary identity.
763        boundary: &'static str,
764        /// Tensor semantic role.
765        role: String,
766    },
767    /// A family boundary declared a non-positive fixed dimension.
768    #[error("architecture boundary {boundary:?} tensor {role:?} has a non-positive dimension")]
769    InvalidTensorDimension {
770        /// Stable boundary identity.
771        boundary: &'static str,
772        /// Tensor semantic role.
773        role: String,
774    },
775    /// A caller supplied non-positive invocation dimensions.
776    #[error(
777        "architecture boundary {boundary:?} requires positive invocation geometry, got batch {batch_size} and sequence {sequence_length}"
778    )]
779    InvalidInvocationGeometry {
780        /// Stable boundary identity.
781        boundary: &'static str,
782        /// Invalid batch size.
783        batch_size: i32,
784        /// Invalid sequence length.
785        sequence_length: i32,
786    },
787    /// A transported payload has the wrong tensor cardinality.
788    #[error(
789        "architecture boundary {boundary:?} expected {expected} tensors but received {actual}"
790    )]
791    TensorCount {
792        /// Stable boundary identity.
793        boundary: &'static str,
794        /// Declared tensor count.
795        expected: usize,
796        /// Transported tensor count.
797        actual: usize,
798    },
799    /// Family-specific boundary validation failed.
800    #[error("architecture boundary {boundary:?} is invalid: {detail}")]
801    Invalid {
802        /// Stable boundary identity.
803        boundary: &'static str,
804        /// Family-owned failure detail.
805        detail: String,
806    },
807}
808
809/// Input, output, and pinned static-module ownership for one partition.
810#[derive(Debug, Clone, Eq, PartialEq)]
811pub struct PartitionOwnership {
812    input: bool,
813    output: bool,
814    static_roles: Vec<String>,
815}
816
817impl PartitionOwnership {
818    /// Creates validated boundary and static-module ownership.
819    pub fn new(
820        input: bool,
821        output: bool,
822        static_roles: impl IntoIterator<Item = impl Into<String>>,
823    ) -> Result<Self, ArchitecturePartitionError> {
824        let static_roles = static_roles.into_iter().map(Into::into).collect::<Vec<_>>();
825        let mut unique = BTreeSet::new();
826        for role in &static_roles {
827            if role.trim().is_empty() {
828                return Err(ArchitecturePartitionError::EmptyStaticRole);
829            }
830            if !unique.insert(role.clone()) {
831                return Err(ArchitecturePartitionError::DuplicateStaticRole(
832                    role.clone(),
833                ));
834            }
835        }
836        Ok(Self {
837            input,
838            output,
839            static_roles,
840        })
841    }
842
843    /// Returns whether this partition owns model input preparation.
844    pub const fn owns_input(&self) -> bool {
845        self.input
846    }
847
848    /// Returns whether this partition owns model output production.
849    pub const fn owns_output(&self) -> bool {
850        self.output
851    }
852
853    /// Returns pinned static roles in architecture declaration order.
854    pub fn static_roles(&self) -> &[String] {
855        &self.static_roles
856    }
857
858    /// Returns whether this partition owns a named pinned static role.
859    pub fn owns_static_role(&self, role: &str) -> bool {
860        self.static_roles.iter().any(|candidate| candidate == role)
861    }
862}
863
864/// Rank-local mutable-state geometry and its architecture-global layer range.
865#[derive(Debug, Clone, Eq, PartialEq)]
866pub struct PartitionState {
867    layout: StateLayout,
868    global_layers: Range<usize>,
869}
870
871impl PartitionState {
872    /// Attaches a local state layout at one architecture-global layer offset.
873    pub fn new(
874        layout: StateLayout,
875        global_layer_offset: usize,
876    ) -> Result<Self, ArchitecturePartitionError> {
877        let end = global_layer_offset.checked_add(layout.len()).ok_or(
878            ArchitecturePartitionError::StateOffsetOverflow {
879                offset: global_layer_offset,
880                layers: layout.len(),
881            },
882        )?;
883        Ok(Self {
884            layout,
885            global_layers: global_layer_offset..end,
886        })
887    }
888
889    /// Returns the exact rank-local state layout.
890    pub const fn layout(&self) -> &StateLayout {
891        &self.layout
892    }
893
894    /// Returns the first architecture-global layer represented by the layout.
895    pub const fn global_layer_offset(&self) -> usize {
896        self.global_layers.start
897    }
898
899    /// Returns the architecture-global state-layer range.
900    pub fn global_layers(&self) -> Range<usize> {
901        self.global_layers.clone()
902    }
903
904    /// Derives prompt-cache identity from this canonical state partition.
905    pub fn prompt_cache_identity<B, M>(
906        &self,
907        architecture: &M,
908        topology: eredu_core::cache::PromptCacheTopology,
909    ) -> Result<eredu_core::cache::PromptCacheModelIdentity, ArchitecturePartitionError>
910    where
911        B: eredu_nn::NeuralBackend,
912        M: crate::ArchitectureParameters<B>,
913        M::DefinitionError: std::fmt::Display,
914    {
915        architecture
916            .state_identity(self, topology)
917            .map_err(|error| ArchitecturePartitionError::ArchitectureState(error.to_string()))?
918            .prompt_cache_identity(self.layout())
919            .map_err(|error| ArchitecturePartitionError::PromptCacheIdentity(error.to_string()))
920    }
921}
922
923/// One validated architecture group and its group-local global unit range.
924#[derive(Debug, Clone, Eq, PartialEq)]
925pub struct PartitionGroup {
926    group: ExecutionGroupId,
927    group_index: usize,
928    global_units: Range<usize>,
929}
930
931impl PartitionGroup {
932    /// Returns the canonical execution-group identity.
933    pub const fn group(&self) -> &ExecutionGroupId {
934        &self.group
935    }
936
937    /// Returns the canonical execution-group slot.
938    pub const fn group_index(&self) -> usize {
939        self.group_index
940    }
941
942    /// Returns owned unit indices in the architecture group's global index space.
943    pub fn global_units(&self) -> Range<usize> {
944        self.global_units.clone()
945    }
946
947    /// Returns whether the range contains one group-local global unit index.
948    pub fn contains(&self, global_unit: usize) -> bool {
949        self.global_units.contains(&global_unit)
950    }
951}
952
953/// Complete backend-neutral realization of one rank's architecture ownership.
954///
955/// `G` is family-owned local construction geometry. `A` is the family-owned
956/// primary and auxiliary wire schema carried by the partition realization.
957#[derive(Debug, Clone)]
958pub struct ArchitecturePartition<G, A> {
959    graph: ExecutionGraph,
960    unit_layout: ExecutionUnitLayout,
961    groups: Vec<PartitionGroup>,
962    ownership: PartitionOwnership,
963    state: Option<PartitionState>,
964    local_geometry: G,
965    boundary_schema: A,
966    parameter_bindings: Vec<OwnedParameterGroupSpec>,
967}
968
969impl<G, A> ArchitecturePartition<G, A> {
970    /// Creates a partition from the topology declared by one concrete neutral
971    /// architecture.
972    ///
973    /// This is the only public constructor: it derives the graph and unit
974    /// layout from `architecture`, preventing a backend realization from
975    /// publishing a parallel topology that merely resembles, but is not the
976    /// canonical topology of, the architecture it will execute.
977    #[allow(clippy::too_many_arguments)]
978    pub fn from_architecture<B, S, M, N>(
979        architecture: &M,
980        group_ranges: impl IntoIterator<Item = (N, Range<usize>)>,
981        ownership: PartitionOwnership,
982        local_geometry: G,
983        boundary_schema: A,
984        parameters: &ArchitectureParameterDescription,
985    ) -> Result<Self, ArchitecturePartitionError>
986    where
987        B: eredu_nn::NeuralBackend,
988        S: crate::RuntimeState<B>,
989        M: crate::LayeredArchitecture<B, S>,
990        M::Error: std::fmt::Display,
991        N: Into<String>,
992        A: ArchitectureBoundary,
993    {
994        let (graph, unit_layout) = canonical_architecture_layout::<B, S, M>(architecture)?;
995        boundary_schema.wire_schema()?;
996        if parameters.graph() != &graph {
997            return Err(ArchitecturePartitionError::ArchitectureGraphMismatch);
998        }
999        if parameters.unit_layout() != &unit_layout {
1000            return Err(ArchitecturePartitionError::ArchitectureUnitLayoutMismatch);
1001        }
1002        let complete_state = architecture
1003            .state_layout()
1004            .map_err(|error| ArchitecturePartitionError::ArchitectureState(error.to_string()))?;
1005        let plan = architecture.state_partition_plan(&complete_state);
1006        let mut partition = Self::new(
1007            graph,
1008            unit_layout,
1009            group_ranges,
1010            ownership,
1011            None,
1012            local_geometry,
1013            boundary_schema,
1014            std::iter::empty(),
1015        )?;
1016        partition.state = partition
1017            .resolve_state_partition(&complete_state, &plan)
1018            .map_err(|error| ArchitecturePartitionError::ArchitectureState(error.to_string()))?;
1019        partition.parameter_bindings = parameters.select_owned(&partition);
1020        Ok(partition)
1021    }
1022
1023    /// Creates one validated rank-local architecture partition after the
1024    /// authoritative architecture topology has already been derived.
1025    #[allow(clippy::too_many_arguments)]
1026    fn new<S>(
1027        graph: ExecutionGraph,
1028        unit_layout: ExecutionUnitLayout,
1029        group_ranges: impl IntoIterator<Item = (S, Range<usize>)>,
1030        ownership: PartitionOwnership,
1031        state: Option<PartitionState>,
1032        local_geometry: G,
1033        boundary_schema: A,
1034        parameter_bindings: impl IntoIterator<Item = OwnedParameterGroupSpec>,
1035    ) -> Result<Self, ArchitecturePartitionError>
1036    where
1037        S: Into<String>,
1038    {
1039        validate_canonical_layout(&graph, &unit_layout)?;
1040        let mut seen_groups = BTreeSet::new();
1041        let mut groups = Vec::new();
1042        for (group, global_units) in group_ranges {
1043            let group = group.into();
1044            let group_index = graph
1045                .groups()
1046                .iter()
1047                .position(|candidate| candidate.id() == group)
1048                .ok_or_else(|| ArchitecturePartitionError::UnknownGroup(group.clone()))?;
1049            if !seen_groups.insert(group.clone()) {
1050                return Err(ArchitecturePartitionError::DuplicateGroup(group));
1051            }
1052            if global_units.is_empty() {
1053                return Err(ArchitecturePartitionError::EmptyGroupRange { group });
1054            }
1055            let available = unit_layout
1056                .group_range(group_index)
1057                .expect("canonical layout contains every graph group")
1058                .len();
1059            if global_units.end > available {
1060                return Err(ArchitecturePartitionError::GroupRangeOutOfBounds {
1061                    group,
1062                    start: global_units.start,
1063                    end: global_units.end,
1064                    available,
1065                });
1066            }
1067            groups.push(PartitionGroup {
1068                group: unit_layout
1069                    .group_id(group_index)
1070                    .expect("canonical layout contains every graph group identity")
1071                    .clone(),
1072                group_index,
1073                global_units,
1074            });
1075        }
1076        groups.sort_by_key(PartitionGroup::group_index);
1077
1078        let parameter_bindings = parameter_bindings.into_iter().collect::<Vec<_>>();
1079        let mut targets = BTreeSet::new();
1080        for binding in &parameter_bindings {
1081            if !binding
1082                .owner()
1083                .is_local_partition_parts(&groups, &ownership)
1084            {
1085                return Err(ArchitecturePartitionError::NonLocalParameterOwner(
1086                    binding.owner().clone(),
1087                ));
1088            }
1089            for member in binding.members() {
1090                if !targets.insert(member.target().to_owned()) {
1091                    return Err(ArchitecturePartitionError::DuplicateParameterTarget(
1092                        member.target().to_owned(),
1093                    ));
1094                }
1095            }
1096        }
1097
1098        Ok(Self {
1099            graph,
1100            unit_layout,
1101            groups,
1102            ownership,
1103            state,
1104            local_geometry,
1105            boundary_schema,
1106            parameter_bindings,
1107        })
1108    }
1109
1110    /// Returns the canonical architecture execution graph.
1111    pub const fn graph(&self) -> &ExecutionGraph {
1112        &self.graph
1113    }
1114
1115    /// Returns the canonical complete execution-unit layout.
1116    pub const fn unit_layout(&self) -> &ExecutionUnitLayout {
1117        &self.unit_layout
1118    }
1119
1120    /// Returns groups and group-local global unit ranges owned by this rank.
1121    pub fn groups(&self) -> &[PartitionGroup] {
1122        &self.groups
1123    }
1124
1125    /// Traverses rank-owned execution units in canonical architecture order.
1126    pub fn units(&self) -> impl Iterator<Item = crate::ExecutionUnitAddress> + '_ {
1127        self.groups.iter().flat_map(move |owned| {
1128            let group = owned.group_index;
1129            let base = self
1130                .unit_layout
1131                .group_range(group)
1132                .expect("partition group belongs to its canonical layout")
1133                .start;
1134            owned.global_units.clone().map(move |index| {
1135                self.unit_layout
1136                    .address(base + index)
1137                    .expect("partition unit belongs to its canonical layout")
1138            })
1139        })
1140    }
1141
1142    /// Returns whether this rank owns one group-local global unit.
1143    pub fn owns_unit(&self, group: &str, global_unit: usize) -> bool {
1144        self.groups
1145            .iter()
1146            .any(|owned| owned.group.as_str() == group && owned.contains(global_unit))
1147    }
1148
1149    /// Returns input, output, and static-module ownership.
1150    pub const fn ownership(&self) -> &PartitionOwnership {
1151        &self.ownership
1152    }
1153
1154    /// Returns rank-local state geometry when this partition owns mutable state.
1155    pub const fn state(&self) -> Option<&PartitionState> {
1156        self.state.as_ref()
1157    }
1158
1159    /// Derives prompt-cache identity from this partition's canonical state.
1160    pub fn prompt_cache_identity<B, M>(
1161        &self,
1162        architecture: &M,
1163        topology: eredu_core::cache::PromptCacheTopology,
1164    ) -> Result<eredu_core::cache::PromptCacheModelIdentity, ArchitecturePartitionError>
1165    where
1166        B: eredu_nn::NeuralBackend,
1167        M: crate::ArchitectureParameters<B>,
1168        M::DefinitionError: std::fmt::Display,
1169    {
1170        let state = self
1171            .state()
1172            .ok_or(ArchitecturePartitionError::MissingArchitectureState)?;
1173        state.prompt_cache_identity::<B, M>(architecture, topology)
1174    }
1175
1176    /// Resolves the architecture-authored state plan for this realized partition.
1177    ///
1178    /// The current partition representation stores one contiguous global state
1179    /// interval. A valid plan may describe multiple semantic ranges, but the
1180    /// ranges selected by any one partition must be adjacent.
1181    pub fn resolve_state_partition(
1182        &self,
1183        complete: &StateLayout,
1184        plan: &ArchitectureStatePartitionPlan,
1185    ) -> Result<Option<PartitionState>, ArchitectureStatePartitionError> {
1186        if plan.rules().is_empty() {
1187            return Err(ArchitectureStatePartitionError::EmptyPlan);
1188        }
1189
1190        let mut rules = plan.rules().iter().collect::<Vec<_>>();
1191        rules.sort_by_key(|rule| rule.layers().start);
1192        let mut frontier = 0usize;
1193        for rule in &rules {
1194            let layers = rule.layers();
1195            if layers.is_empty() {
1196                return Err(ArchitectureStatePartitionError::EmptyRange {
1197                    start: layers.start,
1198                    end: layers.end,
1199                });
1200            }
1201            if layers.end > complete.len() {
1202                return Err(ArchitectureStatePartitionError::RangeOutOfBounds {
1203                    start: layers.start,
1204                    end: layers.end,
1205                    layers: complete.len(),
1206                });
1207            }
1208            if layers.start < frontier {
1209                return Err(ArchitectureStatePartitionError::OverlappingRange {
1210                    start: layers.start,
1211                    frontier,
1212                });
1213            }
1214            if layers.start > frontier {
1215                return Err(ArchitectureStatePartitionError::UnassignedLayer { layer: frontier });
1216            }
1217            if let ArchitectureStatePlacement::GroupUnits { group } = rule.placement() {
1218                let units = self
1219                    .unit_layout
1220                    .group_range(group)
1221                    .ok_or(ArchitectureStatePartitionError::UnknownGroup { group })?
1222                    .len();
1223                if layers.len() != units {
1224                    return Err(ArchitectureStatePartitionError::GroupLengthMismatch {
1225                        group,
1226                        start: layers.start,
1227                        end: layers.end,
1228                        units,
1229                    });
1230                }
1231            }
1232            frontier = layers.end;
1233        }
1234        if frontier != complete.len() {
1235            return Err(ArchitectureStatePartitionError::UnassignedLayer { layer: frontier });
1236        }
1237
1238        let mut selected = Vec::new();
1239        for rule in plan.rules() {
1240            let layers = rule.layers();
1241            match rule.placement() {
1242                ArchitectureStatePlacement::GroupUnits { group } => {
1243                    if let Some(owned) = self
1244                        .groups
1245                        .iter()
1246                        .find(|owned| owned.group_index() == group)
1247                    {
1248                        let units = owned.global_units();
1249                        selected.push(layers.start + units.start..layers.start + units.end);
1250                    }
1251                }
1252                ArchitectureStatePlacement::OutputOwner if self.ownership.owns_output() => {
1253                    selected.push(layers);
1254                }
1255                ArchitectureStatePlacement::OutputOwner => {}
1256            }
1257        }
1258        if selected.is_empty() {
1259            return Ok(None);
1260        }
1261        selected.sort_by_key(|layers| layers.start);
1262        let start = selected[0].start;
1263        let mut end = selected[0].end;
1264        for layers in selected.iter().skip(1) {
1265            if layers.start != end {
1266                return Err(ArchitectureStatePartitionError::DiscontiguousSelection {
1267                    frontier: end,
1268                    start: layers.start,
1269                });
1270            }
1271            end = layers.end;
1272        }
1273        let layout = complete
1274            .slice(start..end)
1275            .map_err(|error| ArchitectureStatePartitionError::InvalidLayout(error.to_string()))?;
1276        PartitionState::new(layout, start)
1277            .map(Some)
1278            .map_err(|error| ArchitectureStatePartitionError::InvalidLayout(error.to_string()))
1279    }
1280
1281    /// Returns family-owned rank-local construction geometry.
1282    pub const fn local_geometry(&self) -> &G {
1283        &self.local_geometry
1284    }
1285
1286    /// Returns the family-owned primary and auxiliary boundary schema.
1287    pub const fn boundary_schema(&self) -> &A {
1288        &self.boundary_schema
1289    }
1290
1291    /// Mutably returns the family-owned primary and auxiliary boundary schema.
1292    pub fn boundary_schema_mut(&mut self) -> &mut A {
1293        &mut self.boundary_schema
1294    }
1295
1296    /// Returns neutral semantic parameter bindings owned by this rank.
1297    pub fn parameter_bindings(&self) -> &[OwnedParameterGroupSpec] {
1298        &self.parameter_bindings
1299    }
1300
1301    /// Returns the exact neutral groups assigned to one architecture owner.
1302    pub fn parameter_bindings_for_owner<'a>(
1303        &'a self,
1304        owner: &'a ParameterGroupOwner,
1305    ) -> impl Iterator<Item = &'a ParameterGroupSpec> + 'a {
1306        self.parameter_bindings
1307            .iter()
1308            .filter(move |binding| binding.owner() == owner)
1309            .map(OwnedParameterGroupSpec::group)
1310    }
1311
1312    /// Proves that this partition still describes the supplied concrete
1313    /// neutral architecture.
1314    ///
1315    /// Loaders may use this when a partition crosses a backend boundary or is
1316    /// restored from a prepared plan. Both dependency edges and exact unit
1317    /// counts are compared; matching group names alone are insufficient.
1318    pub fn validate_architecture<B, S, M>(
1319        &self,
1320        architecture: &M,
1321    ) -> Result<(), ArchitecturePartitionError>
1322    where
1323        B: eredu_nn::NeuralBackend,
1324        S: crate::RuntimeState<B>,
1325        M: crate::LayeredArchitecture<B, S>,
1326        M::Error: std::fmt::Display,
1327    {
1328        let (graph, unit_layout) = canonical_architecture_layout::<B, S, M>(architecture)?;
1329        if graph != self.graph {
1330            return Err(ArchitecturePartitionError::ArchitectureGraphMismatch);
1331        }
1332        if unit_layout != self.unit_layout {
1333            return Err(ArchitecturePartitionError::ArchitectureUnitLayoutMismatch);
1334        }
1335        Ok(())
1336    }
1337}
1338
1339/// Validated execution metadata for one rank-local layered partition.
1340///
1341/// This driver is the single owner of partition input/output checks, canonical
1342/// storage and state ranges, execution-group setup/completion, and final output
1343/// projection. Concrete backends retain only state storage and unit residency.
1344#[derive(Debug, Clone)]
1345pub struct LayeredPartitionDriver {
1346    group: usize,
1347    range: Range<usize>,
1348    state_layout: StateLayout,
1349    owns_input: bool,
1350    owns_output: bool,
1351}
1352
1353impl LayeredPartitionDriver {
1354    /// Validates a canonical partition against its concrete unit storage.
1355    pub fn new<G, A>(
1356        partition: &ArchitecturePartition<G, A>,
1357        group_index: usize,
1358        storage_range: Range<usize>,
1359    ) -> Result<Self, LayeredPartitionError> {
1360        let group = partition
1361            .groups()
1362            .iter()
1363            .find(|group| group.group_index() == group_index)
1364            .ok_or(LayeredPartitionError::GroupNotOwned { group: group_index })?;
1365        let range = group.global_units();
1366        if storage_range != range {
1367            return Err(LayeredPartitionError::StorageRange {
1368                storage: storage_range,
1369                partition: range,
1370            });
1371        }
1372        let state = partition
1373            .state()
1374            .ok_or(LayeredPartitionError::MissingState)?;
1375        if state.global_layers().start > range.start || state.global_layers().end < range.end {
1376            return Err(LayeredPartitionError::StateRange {
1377                state: state.global_layers(),
1378                partition: range,
1379            });
1380        }
1381        Ok(Self {
1382            group: group.group_index(),
1383            range,
1384            state_layout: state.layout().clone(),
1385            owns_input: partition.ownership().owns_input(),
1386            owns_output: partition.ownership().owns_output(),
1387        })
1388    }
1389
1390    /// Returns the canonical group-local global unit range.
1391    pub fn range(&self) -> Range<usize> {
1392        self.range.clone()
1393    }
1394
1395    /// Returns the canonical architecture execution-group slot.
1396    pub const fn group_index(&self) -> usize {
1397        self.group
1398    }
1399
1400    /// Returns the architecture-global state layout for this partition.
1401    pub const fn state_layout(&self) -> &StateLayout {
1402        &self.state_layout
1403    }
1404
1405    /// Validates input form against architecture boundary ownership.
1406    pub fn input<'a, T, A>(
1407        &self,
1408        input: LayeredPartitionInput<'a, T, A>,
1409    ) -> Result<LayeredPartitionInput<'a, T, A>, LayeredPartitionError> {
1410        match (&input, self.owns_input) {
1411            (LayeredPartitionInput::Tokens(_), true)
1412            | (LayeredPartitionInput::Hidden { .. }, _) => Ok(input),
1413            (LayeredPartitionInput::Tokens(_), false) => {
1414                Err(LayeredPartitionError::TokensOnNonInputOwner)
1415            }
1416        }
1417    }
1418
1419    /// Transports this partition's realized boundary through the selected
1420    /// opaque collective group.
1421    ///
1422    /// Keeping the operation on the validated driver makes boundary movement
1423    /// part of partition execution rather than an unrelated backend call.
1424    pub fn exchange_boundary<B>(
1425        &self,
1426        value: B::Tensor,
1427        group: &B::Group,
1428        executor: &B::Executor,
1429    ) -> Result<B::Tensor, B::CollectiveError>
1430    where
1431        B: crate::CollectiveBackend,
1432    {
1433        B::all_to_all(value, group, executor)
1434    }
1435
1436    /// Prepares the partition and starts its canonical execution group.
1437    #[allow(clippy::too_many_arguments)]
1438    pub fn begin<'a, B, S, M>(
1439        &self,
1440        architecture: &mut M,
1441        input: LayeredPartitionInput<
1442            'a,
1443            B::Tensor,
1444            <M::Boundary as ArchitectureBoundary>::Boundary<B::Tensor>,
1445        >,
1446        mask: Option<&B::Tensor>,
1447        state: &mut S,
1448        parallel: Option<&B::ParallelContext>,
1449        context: &<B::Tensor as eredu_nn::Tensor>::Context,
1450    ) -> Result<LayeredForwardState<B::Tensor, M::ForwardContext>, M::Error>
1451    where
1452        B: eredu_nn::NeuralBackend,
1453        S: RuntimeState<B>,
1454        M: PartitionedLayeredArchitecture<B, S>,
1455    {
1456        let mut forward = match parallel {
1457            Some(parallel) => architecture.begin_partition_parallel(
1458                input,
1459                mask,
1460                state,
1461                &self.state_layout,
1462                self.range.start,
1463                parallel,
1464                context,
1465            ),
1466            None => architecture.begin_partition(
1467                input,
1468                mask,
1469                state,
1470                &self.state_layout,
1471                self.range.start,
1472                context,
1473            ),
1474        }?;
1475        forward.hidden = architecture.enter_partition_group(
1476            self.group,
1477            &forward.hidden,
1478            state,
1479            &mut forward.context,
1480            parallel,
1481            context,
1482        )?;
1483        Ok(forward)
1484    }
1485
1486    /// Completes the canonical group and applies output projection only on its owner.
1487    #[allow(
1488        clippy::too_many_arguments,
1489        clippy::type_complexity,
1490        reason = "the signature exposes the backend and architecture boundary types explicitly"
1491    )]
1492    pub fn finish<B, S, M>(
1493        &self,
1494        architecture: &mut M,
1495        hidden: &B::Tensor,
1496        state: &mut S,
1497        forward: &mut M::ForwardContext,
1498        parallel: Option<&B::ParallelContext>,
1499        context: &<B::Tensor as eredu_nn::Tensor>::Context,
1500    ) -> Result<
1501        LayeredPartitionOutput<
1502            B::Tensor,
1503            <M::Boundary as ArchitectureBoundary>::Boundary<B::Tensor>,
1504        >,
1505        M::Error,
1506    >
1507    where
1508        B: eredu_nn::NeuralBackend,
1509        S: RuntimeState<B>,
1510        M: PartitionedLayeredArchitecture<B, S>,
1511    {
1512        let hidden = architecture
1513            .leave_partition_group(self.group, hidden, state, forward, parallel, context)?;
1514        architecture.finish_partition(&hidden, state, forward, self.owns_output, parallel, context)
1515    }
1516}
1517
1518/// Invalid concrete realization or boundary use of a layered partition.
1519#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
1520#[non_exhaustive]
1521pub enum LayeredPartitionError {
1522    /// The selected architecture execution group is not owned by this partition.
1523    #[error("layered partition does not own execution group {group}")]
1524    GroupNotOwned {
1525        /// Canonical architecture group index.
1526        group: usize,
1527    },
1528    /// Concrete unit storage does not match canonical ownership.
1529    #[error("partition storage range {storage:?} disagrees with canonical range {partition:?}")]
1530    StorageRange {
1531        /// Concrete backend storage range.
1532        storage: Range<usize>,
1533        /// Canonical partition range.
1534        partition: Range<usize>,
1535    },
1536    /// Partition omitted mutable state geometry.
1537    #[error("layered partition has no runtime state")]
1538    MissingState,
1539    /// Mutable state geometry does not match canonical unit ownership.
1540    #[error("partition state range {state:?} disagrees with canonical range {partition:?}")]
1541    StateRange {
1542        /// Architecture-global state range.
1543        state: Range<usize>,
1544        /// Canonical partition range.
1545        partition: Range<usize>,
1546    },
1547    /// Token ids were supplied after the architecture input boundary.
1548    #[error("non-input partition received token ids")]
1549    TokensOnNonInputOwner,
1550}
1551
1552fn canonical_architecture_layout<B, S, M>(
1553    architecture: &M,
1554) -> Result<(ExecutionGraph, ExecutionUnitLayout), ArchitecturePartitionError>
1555where
1556    B: eredu_nn::NeuralBackend,
1557    S: crate::RuntimeState<B>,
1558    M: crate::LayeredArchitecture<B, S>,
1559    M::Error: std::fmt::Display,
1560{
1561    let graph = architecture
1562        .execution_graph()
1563        .map_err(|error| ArchitecturePartitionError::ArchitectureTopology(error.to_string()))?;
1564    let primary = architecture.primary_execution_group();
1565    let primary_index = graph.group_index(primary).ok_or_else(|| {
1566        ArchitecturePartitionError::ArchitectureTopology(format!(
1567            "primary execution group {primary:?} is not present in the canonical graph"
1568        ))
1569    })?;
1570    let primary_transport = architecture.group_transport(primary_index);
1571    if primary_transport.kind != crate::ArchitectureGroupKind::Decoder
1572        || primary_transport.placement != crate::ArchitectureGroupPlacement::Pipeline
1573    {
1574        return Err(ArchitecturePartitionError::ArchitectureTopology(format!(
1575            "primary execution group {primary:?} must be a pipeline decoder"
1576        )));
1577    }
1578    let mut declared_groups = BTreeSet::from([primary.to_owned()]);
1579    for prediction in architecture.prediction_execution_groups() {
1580        let prediction_index = graph.group_index(&prediction).ok_or_else(|| {
1581            ArchitecturePartitionError::ArchitectureTopology(format!(
1582                "prediction execution group {prediction:?} is not present in the canonical graph"
1583            ))
1584        })?;
1585        let prediction_transport = architecture.group_transport(prediction_index);
1586        if prediction_transport.kind != crate::ArchitectureGroupKind::Prediction
1587            || prediction_transport.placement != crate::ArchitectureGroupPlacement::OutputOwner
1588        {
1589            return Err(ArchitecturePartitionError::ArchitectureTopology(format!(
1590                "prediction execution group {prediction:?} must be an output-owner prediction"
1591            )));
1592        }
1593        if !declared_groups.insert(prediction.clone()) {
1594            return Err(ArchitecturePartitionError::ArchitectureTopology(format!(
1595                "execution group {prediction:?} is declared as a primary or prediction group more than once"
1596            )));
1597        }
1598    }
1599    let mut counts = Vec::with_capacity(graph.groups().len());
1600    let mut paths = BTreeSet::new();
1601    for group in 0..graph.groups().len() {
1602        let count = architecture
1603            .group_unit_count(group)
1604            .map_err(|error| ArchitecturePartitionError::ArchitectureTopology(error.to_string()))?;
1605        counts.push(count);
1606        for index in 0..count {
1607            let path = architecture.unit_path(group, index).map_err(|error| {
1608                ArchitecturePartitionError::ArchitectureTopology(error.to_string())
1609            })?;
1610            if path.trim().is_empty() {
1611                return Err(ArchitecturePartitionError::EmptyArchitectureUnitPath { group, index });
1612            }
1613            if !paths.insert(path.clone()) {
1614                return Err(ArchitecturePartitionError::DuplicateArchitectureUnitPath(
1615                    path,
1616                ));
1617            }
1618        }
1619    }
1620    let unit_layout = ExecutionUnitLayout::new(&graph, counts)
1621        .map_err(|error| ArchitecturePartitionError::ArchitectureTopology(error.to_string()))?;
1622    Ok((graph, unit_layout))
1623}
1624
1625fn validate_canonical_layout(
1626    graph: &ExecutionGraph,
1627    layout: &ExecutionUnitLayout,
1628) -> Result<(), ArchitecturePartitionError> {
1629    if graph.groups().len() != layout.group_count() {
1630        return Err(ArchitecturePartitionError::LayoutGroupCountMismatch {
1631            graph: graph.groups().len(),
1632            layout: layout.group_count(),
1633        });
1634    }
1635    for (index, group) in graph.groups().iter().enumerate() {
1636        let layout_group = layout
1637            .group_id(index)
1638            .expect("matching group counts provide every layout identity");
1639        if layout_group.as_str() != group.id() {
1640            return Err(ArchitecturePartitionError::LayoutGroupMismatch {
1641                index,
1642                graph: group.id().to_owned(),
1643                layout: layout_group.as_str().to_owned(),
1644            });
1645        }
1646    }
1647    Ok(())
1648}
1649
1650/// Invalid backend-neutral architecture partition declaration.
1651#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
1652#[non_exhaustive]
1653pub enum ArchitecturePartitionError {
1654    /// The architecture supplied an invalid partition-boundary wire schema.
1655    #[error("invalid architecture partition boundary: {0}")]
1656    InvalidBoundary(#[from] ArchitectureBoundaryError),
1657    /// The neutral architecture could not declare a canonical graph, unit
1658    /// count, or unit path.
1659    #[error("neutral architecture topology is invalid: {0}")]
1660    ArchitectureTopology(String),
1661    /// The architecture could not declare or partition its mutable state.
1662    #[error("neutral architecture state is invalid: {0}")]
1663    ArchitectureState(String),
1664    /// The realized partition owns no mutable architecture state.
1665    #[error("architecture partition owns no mutable state")]
1666    MissingArchitectureState,
1667    /// The architecture state could not be converted to prompt-cache identity.
1668    #[error("architecture prompt-cache identity is invalid: {0}")]
1669    PromptCacheIdentity(String),
1670    /// A neutral architecture exposed an empty stable unit path.
1671    #[error("neutral architecture unit {group}:{index} has an empty path")]
1672    EmptyArchitectureUnitPath {
1673        /// Canonical execution-group slot.
1674        group: usize,
1675        /// Group-local unit index.
1676        index: usize,
1677    },
1678    /// Two canonical architecture units exposed the same stable path.
1679    #[error("neutral architecture repeats unit path {0:?}")]
1680    DuplicateArchitectureUnitPath(String),
1681    /// The partition dependency graph differs from the concrete architecture.
1682    #[error("architecture partition dependency graph differs from the neutral architecture")]
1683    ArchitectureGraphMismatch,
1684    /// The partition unit counts differ from the concrete architecture.
1685    #[error("architecture partition unit layout differs from the neutral architecture")]
1686    ArchitectureUnitLayoutMismatch,
1687    /// The graph and complete unit layout contain different group counts.
1688    #[error("execution graph contains {graph} groups but its unit layout contains {layout}")]
1689    LayoutGroupCountMismatch {
1690        /// Canonical graph group count.
1691        graph: usize,
1692        /// Unit-layout group count.
1693        layout: usize,
1694    },
1695    /// A unit-layout group identity differs from the graph at the same slot.
1696    #[error("execution group {index} is {graph:?} in the graph but {layout:?} in the unit layout")]
1697    LayoutGroupMismatch {
1698        /// Canonical group slot.
1699        index: usize,
1700        /// Graph identity.
1701        graph: String,
1702        /// Unit-layout identity.
1703        layout: String,
1704    },
1705    /// A rank-local unit range names no canonical architecture group.
1706    #[error("architecture partition names unknown execution group {0:?}")]
1707    UnknownGroup(String),
1708    /// A canonical architecture group was declared more than once.
1709    #[error("architecture partition repeats execution group {0:?}")]
1710    DuplicateGroup(String),
1711    /// A group owns no execution units.
1712    #[error("architecture partition declares an empty unit range for group {group:?}")]
1713    EmptyGroupRange {
1714        /// Canonical group identity.
1715        group: String,
1716    },
1717    /// A group-local global unit range exceeds the canonical group size.
1718    #[error(
1719        "architecture partition range {start}..{end} for group {group:?} exceeds {available} units"
1720    )]
1721    GroupRangeOutOfBounds {
1722        /// Canonical group identity.
1723        group: String,
1724        /// Invalid range start.
1725        start: usize,
1726        /// Invalid range end.
1727        end: usize,
1728        /// Canonical group unit count.
1729        available: usize,
1730    },
1731    /// A static ownership role is blank.
1732    #[error("architecture partition static role must not be empty")]
1733    EmptyStaticRole,
1734    /// A static ownership role was repeated.
1735    #[error("architecture partition repeats static role {0:?}")]
1736    DuplicateStaticRole(String),
1737    /// A local state layout cannot be placed in the global layer index space.
1738    #[error("state layer offset {offset} plus {layers} local layers overflowed usize")]
1739    StateOffsetOverflow {
1740        /// Requested global layer offset.
1741        offset: usize,
1742        /// Local state-layer count.
1743        layers: usize,
1744    },
1745    /// Two semantic parameter groups claim the same physical target.
1746    #[error("architecture partition repeats parameter target {0:?}")]
1747    DuplicateParameterTarget(String),
1748    /// A supplied parameter owner is not part of this rank-local partition.
1749    #[error("architecture partition includes non-local parameter owner {0:?}")]
1750    NonLocalParameterOwner(ParameterGroupOwner),
1751}
1752
1753#[cfg(test)]
1754mod tests {
1755    use super::*;
1756    use crate::{MemberSharding, ParameterMemberSpec, ParameterRole};
1757    use eredu_core::{cache::LayerCachePolicy, LayerSchedule};
1758
1759    #[derive(Debug, Clone, Eq, PartialEq)]
1760    struct Geometry(&'static str);
1761
1762    #[derive(Debug, Clone, Eq, PartialEq)]
1763    struct Boundary {
1764        route: usize,
1765    }
1766
1767    #[derive(Debug, Clone, Eq, PartialEq)]
1768    struct PairBoundary<T> {
1769        tokens: T,
1770        embedded: T,
1771    }
1772
1773    #[derive(Debug, Clone, Copy)]
1774    struct PairBoundarySchema;
1775
1776    impl ArchitectureBoundary for PairBoundarySchema {
1777        type Boundary<T> = PairBoundary<T>;
1778
1779        const IDENTITY: &'static str = "fixture.target";
1780
1781        fn primary_tensor_spec(&self) -> BoundaryTensorSpec {
1782            BoundaryTensorSpec::primary_activation(8)
1783        }
1784
1785        fn auxiliary_tensor_specs(&self) -> Vec<BoundaryTensorSpec> {
1786            vec![
1787                BoundaryTensorSpec::new(
1788                    "tokens",
1789                    [
1790                        BoundaryTensorDimension::Batch,
1791                        BoundaryTensorDimension::Sequence,
1792                    ],
1793                    BoundaryTensorDtype::Uint32,
1794                ),
1795                BoundaryTensorSpec::new(
1796                    "embedded",
1797                    [
1798                        BoundaryTensorDimension::Batch,
1799                        BoundaryTensorDimension::Sequence,
1800                        BoundaryTensorDimension::Fixed(16),
1801                    ],
1802                    BoundaryTensorDtype::Activation,
1803                ),
1804            ]
1805        }
1806
1807        fn encode<T>(
1808            &self,
1809            boundary: Self::Boundary<T>,
1810        ) -> Result<Vec<T>, ArchitectureBoundaryError> {
1811            Ok(vec![boundary.tokens, boundary.embedded])
1812        }
1813
1814        fn decode<T>(
1815            &self,
1816            mut tensors: Vec<T>,
1817        ) -> Result<Self::Boundary<T>, ArchitectureBoundaryError> {
1818            validate_boundary_tensor_count(self, &tensors)?;
1819            let embedded = tensors.pop().expect("validated embedded tensor");
1820            let tokens = tensors.pop().expect("validated token tensor");
1821            Ok(PairBoundary { tokens, embedded })
1822        }
1823    }
1824
1825    fn graph() -> ExecutionGraph {
1826        ExecutionGraph::chain(["primary", "prediction"]).unwrap()
1827    }
1828
1829    fn layout(graph: &ExecutionGraph) -> ExecutionUnitLayout {
1830        ExecutionUnitLayout::new(graph, [4, 3]).unwrap()
1831    }
1832
1833    fn state_layout(layers: usize) -> StateLayout {
1834        StateLayout::new(
1835            LayerSchedule::new(layers, vec![LayerCachePolicy::NoState; layers]).unwrap(),
1836        )
1837        .unwrap()
1838    }
1839
1840    fn parameter(logical: &str, target: &str) -> ParameterGroupSpec {
1841        ParameterGroupSpec::new(
1842            logical,
1843            ParameterRole::Replicated,
1844            [ParameterMemberSpec::new(
1845                target,
1846                vec![2, 2],
1847                MemberSharding::Replicated,
1848            )],
1849        )
1850        .unwrap()
1851    }
1852
1853    fn valid_partition() -> ArchitecturePartition<Geometry, Boundary> {
1854        let graph = graph();
1855        let layout = layout(&graph);
1856        ArchitecturePartition::new(
1857            graph,
1858            layout,
1859            [("prediction", 0..2), ("primary", 1..4)],
1860            PartitionOwnership::new(true, false, ["embedding", "normalization"]).unwrap(),
1861            Some(PartitionState::new(state_layout(2), 7).unwrap()),
1862            Geometry("local"),
1863            Boundary { route: 3 },
1864            [
1865                OwnedParameterGroupSpec::new(
1866                    ParameterGroupOwner::static_role("embedding"),
1867                    parameter("model.embed_tokens", "model.embed_tokens.weight"),
1868                ),
1869                OwnedParameterGroupSpec::new(
1870                    ParameterGroupOwner::execution_unit(
1871                        ExecutionGroupId::new("primary").unwrap(),
1872                        1,
1873                    ),
1874                    parameter("model.layers.1", "model.layers.1.weight"),
1875                ),
1876            ],
1877        )
1878        .unwrap()
1879    }
1880
1881    fn state_plan_partition(
1882        primary: Range<usize>,
1883        ownership: PartitionOwnership,
1884    ) -> ArchitecturePartition<(), ()> {
1885        let graph = graph();
1886        ArchitecturePartition::new(
1887            graph.clone(),
1888            layout(&graph),
1889            [("primary", primary)],
1890            ownership,
1891            None,
1892            (),
1893            (),
1894            [],
1895        )
1896        .unwrap()
1897    }
1898
1899    #[test]
1900    fn architecture_state_plan_attaches_declared_tail_to_output_owner() {
1901        let complete = state_layout(6);
1902        let plan = ArchitectureStatePartitionPlan::new([
1903            crate::ArchitectureStatePartitionRule::group_units(0, 0..4),
1904            crate::ArchitectureStatePartitionRule::output_owner(4..6),
1905        ]);
1906        let interior = state_plan_partition(
1907            1..3,
1908            PartitionOwnership::new(false, false, std::iter::empty::<&str>()).unwrap(),
1909        );
1910        let output = state_plan_partition(
1911            3..4,
1912            PartitionOwnership::new(false, true, std::iter::empty::<&str>()).unwrap(),
1913        );
1914
1915        assert_eq!(
1916            interior
1917                .resolve_state_partition(&complete, &plan)
1918                .unwrap()
1919                .unwrap()
1920                .global_layers(),
1921            1..3
1922        );
1923        assert_eq!(
1924            output
1925                .resolve_state_partition(&complete, &plan)
1926                .unwrap()
1927                .unwrap()
1928                .global_layers(),
1929            3..6
1930        );
1931    }
1932
1933    #[test]
1934    fn architecture_state_plan_rejects_noncontiguous_local_state() {
1935        let complete = state_layout(6);
1936        let plan = ArchitectureStatePartitionPlan::new([
1937            crate::ArchitectureStatePartitionRule::output_owner(0..2),
1938            crate::ArchitectureStatePartitionRule::group_units(0, 2..6),
1939        ]);
1940        let output = state_plan_partition(
1941            3..4,
1942            PartitionOwnership::new(false, true, std::iter::empty::<&str>()).unwrap(),
1943        );
1944
1945        assert_eq!(
1946            output.resolve_state_partition(&complete, &plan),
1947            Err(ArchitectureStatePartitionError::DiscontiguousSelection {
1948                frontier: 2,
1949                start: 5,
1950            })
1951        );
1952    }
1953
1954    fn parameter_description(
1955        expected: Vec<ParameterGroupSpec>,
1956        groups: Vec<OwnedParameterGroupSpec>,
1957    ) -> Result<ArchitectureParameterDescription, ArchitectureParameterError> {
1958        let graph = graph();
1959        ArchitectureParameterDescription::new(&graph, &layout(&graph), expected, groups)
1960    }
1961
1962    #[test]
1963    fn parameter_description_selects_static_roles_and_canonical_units() {
1964        let embedding = parameter("embedding", "model.embed_tokens.weight");
1965        let layer = parameter("layer", "model.layers.1.weight");
1966        let description = parameter_description(
1967            vec![embedding.clone(), layer.clone()],
1968            vec![
1969                OwnedParameterGroupSpec::new(
1970                    ParameterGroupOwner::static_role("embedding"),
1971                    embedding,
1972                ),
1973                OwnedParameterGroupSpec::new(
1974                    ParameterGroupOwner::execution_unit(
1975                        ExecutionGroupId::new("primary").unwrap(),
1976                        1,
1977                    ),
1978                    layer,
1979                ),
1980            ],
1981        )
1982        .unwrap();
1983        let partition = valid_partition();
1984        assert_eq!(description.graph(), partition.graph());
1985        assert_eq!(description.unit_layout(), partition.unit_layout());
1986        let selected = description.select_owned(&partition);
1987        assert_eq!(selected.len(), 2);
1988        assert_eq!(selected[0].logical_name(), "embedding");
1989        assert_eq!(selected[1].logical_name(), "layer");
1990        assert_eq!(
1991            selected[0].owner(),
1992            &ParameterGroupOwner::static_role("embedding")
1993        );
1994        assert_eq!(
1995            selected[1].owner(),
1996            &ParameterGroupOwner::execution_unit(ExecutionGroupId::new("primary").unwrap(), 1,)
1997        );
1998    }
1999
2000    #[test]
2001    fn parameter_description_selects_every_owned_target_for_a_role() {
2002        let expert = ParameterGroupSpec::new(
2003            "model.layers.1.expert_intermediate",
2004            ParameterRole::ExpertIntermediate,
2005            [
2006                ParameterMemberSpec::new(
2007                    "model.layers.1.moe.packed.weight",
2008                    vec![4, 2],
2009                    MemberSharding::Replicated,
2010                ),
2011                ParameterMemberSpec::new(
2012                    "model.layers.1.moe.packed.scales",
2013                    vec![4, 1],
2014                    MemberSharding::Replicated,
2015                ),
2016                ParameterMemberSpec::new(
2017                    "model.layers.1.moe.alias.biases",
2018                    vec![4, 1],
2019                    MemberSharding::Replicated,
2020                ),
2021            ],
2022        )
2023        .unwrap();
2024        let replicated = parameter("router", "model.layers.1.moe.router.weight");
2025        let owner =
2026            ParameterGroupOwner::execution_unit(ExecutionGroupId::new("primary").unwrap(), 1);
2027        let description = parameter_description(
2028            vec![expert.clone(), replicated.clone()],
2029            vec![
2030                OwnedParameterGroupSpec::new(owner.clone(), expert),
2031                OwnedParameterGroupSpec::new(owner, replicated),
2032            ],
2033        )
2034        .unwrap();
2035
2036        assert_eq!(
2037            description.targets_for_role(ParameterRole::ExpertIntermediate),
2038            BTreeSet::from([
2039                "model.layers.1.moe.alias.biases".to_owned(),
2040                "model.layers.1.moe.packed.scales".to_owned(),
2041                "model.layers.1.moe.packed.weight".to_owned(),
2042            ])
2043        );
2044    }
2045
2046    #[test]
2047    fn parameter_description_selects_shared_static_owner_by_any_consumer() {
2048        let embedding = parameter("embedding", "model.embed_tokens.weight");
2049        let description = parameter_description(
2050            vec![embedding.clone()],
2051            vec![OwnedParameterGroupSpec::new(
2052                ParameterGroupOwner::static_any_of(["output", "embedding"]),
2053                embedding,
2054            )],
2055        )
2056        .unwrap();
2057        assert_eq!(description.select_owned(&valid_partition()).len(), 1);
2058
2059        let duplicate = parameter("embedding", "model.embed_tokens.weight");
2060        assert_eq!(
2061            parameter_description(
2062                vec![duplicate.clone()],
2063                vec![OwnedParameterGroupSpec::new(
2064                    ParameterGroupOwner::static_any_of(["embedding", "embedding"]),
2065                    duplicate,
2066                )],
2067            )
2068            .unwrap_err(),
2069            ArchitectureParameterError::DuplicateStaticRole,
2070        );
2071    }
2072
2073    #[test]
2074    fn partition_rejects_parameter_owner_outside_local_unit_ranges() {
2075        let graph = graph();
2076        let error = ArchitecturePartition::new(
2077            graph.clone(),
2078            layout(&graph),
2079            [("primary", 1..4)],
2080            PartitionOwnership::new(false, false, ["embedding"]).unwrap(),
2081            None,
2082            (),
2083            (),
2084            [OwnedParameterGroupSpec::new(
2085                ParameterGroupOwner::execution_unit(
2086                    ExecutionGroupId::new("prediction").unwrap(),
2087                    0,
2088                ),
2089                parameter("prediction", "prediction.weight"),
2090            )],
2091        )
2092        .unwrap_err();
2093        assert!(matches!(
2094            error,
2095            ArchitecturePartitionError::NonLocalParameterOwner(
2096                ParameterGroupOwner::ExecutionUnit { .. }
2097            )
2098        ));
2099    }
2100
2101    #[test]
2102    fn parameter_description_rejects_missing_duplicate_and_out_of_range_ownership() {
2103        let embedding = parameter("embedding", "model.embed_tokens.weight");
2104        let layer = parameter("layer", "model.layers.1.weight");
2105        assert_eq!(
2106            parameter_description(
2107                vec![embedding.clone(), layer.clone()],
2108                vec![OwnedParameterGroupSpec::new(
2109                    ParameterGroupOwner::static_role("embedding"),
2110                    embedding.clone(),
2111                )],
2112            )
2113            .unwrap_err(),
2114            ArchitectureParameterError::MissingOwnership("model.layers.1.weight".into())
2115        );
2116        assert!(matches!(
2117            parameter_description(
2118                vec![embedding.clone()],
2119                vec![
2120                    OwnedParameterGroupSpec::new(
2121                        ParameterGroupOwner::static_role("embedding"),
2122                        embedding.clone(),
2123                    ),
2124                    OwnedParameterGroupSpec::new(
2125                        ParameterGroupOwner::static_role("output"),
2126                        embedding.clone(),
2127                    ),
2128                ],
2129            )
2130            .unwrap_err(),
2131            ArchitectureParameterError::DuplicateOwnership { .. }
2132        ));
2133        assert_eq!(
2134            parameter_description(
2135                vec![layer.clone()],
2136                vec![OwnedParameterGroupSpec::new(
2137                    ParameterGroupOwner::execution_unit(
2138                        ExecutionGroupId::new("prediction").unwrap(),
2139                        3,
2140                    ),
2141                    layer,
2142                )],
2143            )
2144            .unwrap_err(),
2145            ArchitectureParameterError::UnitOutOfRange {
2146                group: "prediction".into(),
2147                global_unit: 3,
2148                available: 3,
2149            }
2150        );
2151    }
2152
2153    #[test]
2154    fn retains_canonical_topology_ownership_and_typed_family_values() {
2155        let mut partition = valid_partition();
2156        assert_eq!(partition.graph().groups().len(), 2);
2157        assert_eq!(partition.unit_layout().len(), 7);
2158        assert_eq!(partition.groups()[0].group().as_str(), "primary");
2159        assert_eq!(partition.groups()[0].group_index(), 0);
2160        assert_eq!(partition.groups()[0].global_units(), 1..4);
2161        assert!(partition.owns_unit("primary", 3));
2162        assert!(!partition.owns_unit("primary", 0));
2163        assert!(partition.ownership().owns_input());
2164        assert!(!partition.ownership().owns_output());
2165        assert!(partition.ownership().owns_static_role("embedding"));
2166        assert_eq!(
2167            partition
2168                .units()
2169                .map(|unit| (unit.group(), unit.index()))
2170                .collect::<Vec<_>>(),
2171            [(0, 1), (0, 2), (0, 3), (1, 0), (1, 1)]
2172        );
2173        assert_eq!(partition.state().unwrap().global_layers(), 7..9);
2174        assert_eq!(partition.local_geometry(), &Geometry("local"));
2175        partition.boundary_schema_mut().route = 5;
2176        assert_eq!(partition.boundary_schema().route, 5);
2177        assert_eq!(partition.parameter_bindings().len(), 2);
2178    }
2179
2180    #[test]
2181    fn typed_boundary_owns_roles_order_and_atomic_cardinality_validation() {
2182        let boundary = PairBoundary {
2183            tokens: 3,
2184            embedded: 7,
2185        };
2186        let schema = PairBoundarySchema;
2187        let tensors = schema.encode(boundary).unwrap();
2188        assert_eq!(tensors, [3, 7]);
2189        assert_eq!(
2190            schema.decode(tensors).unwrap(),
2191            PairBoundary {
2192                tokens: 3,
2193                embedded: 7
2194            }
2195        );
2196        let resolved = schema.wire_schema().unwrap().resolve(2, 3).unwrap();
2197        assert_eq!(resolved.primary().shape(), [2, 3, 8]);
2198        assert_eq!(resolved.primary().dtype(), BoundaryTensorDtype::Activation);
2199        assert_eq!(resolved.auxiliary()[0].shape(), [2, 3]);
2200        assert_eq!(resolved.auxiliary()[0].dtype(), BoundaryTensorDtype::Uint32);
2201        assert_eq!(resolved.auxiliary()[1].shape(), [2, 3, 16]);
2202        assert_eq!(
2203            resolved.auxiliary()[1].dtype(),
2204            BoundaryTensorDtype::Activation
2205        );
2206        assert_eq!(
2207            schema.decode(vec![3]).unwrap_err(),
2208            ArchitectureBoundaryError::TensorCount {
2209                boundary: "fixture.target",
2210                expected: 2,
2211                actual: 1,
2212            }
2213        );
2214    }
2215
2216    #[test]
2217    fn boundary_schema_rejects_role_and_geometry_drift_before_transport() {
2218        let invalid_primary = BoundaryWireSchema::new(
2219            "fixture.invalid",
2220            BoundaryTensorSpec::new(
2221                "hidden",
2222                [BoundaryTensorDimension::Fixed(8)],
2223                BoundaryTensorDtype::Uint32,
2224            ),
2225            [],
2226        )
2227        .unwrap_err();
2228        assert_eq!(
2229            invalid_primary,
2230            ArchitectureBoundaryError::InvalidPrimaryDtype {
2231                boundary: "fixture.invalid",
2232            }
2233        );
2234
2235        let duplicate = BoundaryWireSchema::new(
2236            "fixture.invalid",
2237            BoundaryTensorSpec::primary_activation(8),
2238            [
2239                BoundaryTensorSpec::new(
2240                    "state",
2241                    [BoundaryTensorDimension::Fixed(1)],
2242                    BoundaryTensorDtype::Activation,
2243                ),
2244                BoundaryTensorSpec::new(
2245                    "state",
2246                    [BoundaryTensorDimension::Fixed(2)],
2247                    BoundaryTensorDtype::Activation,
2248                ),
2249            ],
2250        )
2251        .unwrap_err();
2252        assert_eq!(
2253            duplicate,
2254            ArchitectureBoundaryError::DuplicateTensorRole {
2255                boundary: "fixture.invalid",
2256                role: "state".into(),
2257            }
2258        );
2259
2260        let invalid = BoundaryWireSchema::new(
2261            "fixture.invalid",
2262            BoundaryTensorSpec::primary_activation(8),
2263            [BoundaryTensorSpec::new(
2264                "state",
2265                [BoundaryTensorDimension::Fixed(0)],
2266                BoundaryTensorDtype::Activation,
2267            )],
2268        )
2269        .unwrap_err();
2270        assert_eq!(
2271            invalid,
2272            ArchitectureBoundaryError::InvalidTensorDimension {
2273                boundary: "fixture.invalid",
2274                role: "state".into(),
2275            }
2276        );
2277    }
2278
2279    #[test]
2280    fn rejects_noncanonical_unknown_and_duplicate_groups() {
2281        let graph = graph();
2282        let mismatched_graph = ExecutionGraph::chain(["primary", "other"]).unwrap();
2283        let error = ArchitecturePartition::new(
2284            graph.clone(),
2285            layout(&mismatched_graph),
2286            [("primary", 0..1)],
2287            PartitionOwnership::new(false, false, std::iter::empty::<String>()).unwrap(),
2288            None,
2289            (),
2290            (),
2291            std::iter::empty(),
2292        )
2293        .unwrap_err();
2294        assert!(matches!(
2295            error,
2296            ArchitecturePartitionError::LayoutGroupMismatch { .. }
2297        ));
2298
2299        let error = ArchitecturePartition::new(
2300            graph.clone(),
2301            layout(&graph),
2302            [("missing", 0..1)],
2303            PartitionOwnership::new(false, false, std::iter::empty::<String>()).unwrap(),
2304            None,
2305            (),
2306            (),
2307            std::iter::empty(),
2308        )
2309        .unwrap_err();
2310        assert_eq!(
2311            error,
2312            ArchitecturePartitionError::UnknownGroup("missing".into())
2313        );
2314
2315        let error = ArchitecturePartition::new(
2316            graph.clone(),
2317            layout(&graph),
2318            [("primary", 0..1), ("primary", 1..2)],
2319            PartitionOwnership::new(false, false, std::iter::empty::<String>()).unwrap(),
2320            None,
2321            (),
2322            (),
2323            std::iter::empty(),
2324        )
2325        .unwrap_err();
2326        assert_eq!(
2327            error,
2328            ArchitecturePartitionError::DuplicateGroup("primary".into())
2329        );
2330    }
2331
2332    #[test]
2333    fn rejects_empty_and_out_of_bounds_group_ranges() {
2334        let graph = graph();
2335        let error = ArchitecturePartition::new(
2336            graph.clone(),
2337            layout(&graph),
2338            [("primary", 2..2)],
2339            PartitionOwnership::new(false, false, std::iter::empty::<String>()).unwrap(),
2340            None,
2341            (),
2342            (),
2343            std::iter::empty(),
2344        )
2345        .unwrap_err();
2346        assert!(matches!(
2347            error,
2348            ArchitecturePartitionError::EmptyGroupRange { .. }
2349        ));
2350
2351        let error = ArchitecturePartition::new(
2352            graph.clone(),
2353            layout(&graph),
2354            [("prediction", 1..4)],
2355            PartitionOwnership::new(false, false, std::iter::empty::<String>()).unwrap(),
2356            None,
2357            (),
2358            (),
2359            std::iter::empty(),
2360        )
2361        .unwrap_err();
2362        assert!(matches!(
2363            error,
2364            ArchitecturePartitionError::GroupRangeOutOfBounds { .. }
2365        ));
2366    }
2367
2368    #[test]
2369    fn rejects_state_offset_overflow() {
2370        assert_eq!(
2371            PartitionState::new(state_layout(2), usize::MAX).unwrap_err(),
2372            ArchitecturePartitionError::StateOffsetOverflow {
2373                offset: usize::MAX,
2374                layers: 2,
2375            }
2376        );
2377    }
2378
2379    #[test]
2380    fn rejects_empty_static_roles_and_duplicate_parameter_targets() {
2381        assert_eq!(
2382            PartitionOwnership::new(false, false, [" "]).unwrap_err(),
2383            ArchitecturePartitionError::EmptyStaticRole
2384        );
2385
2386        let graph = graph();
2387        let error = ArchitecturePartition::new(
2388            graph.clone(),
2389            layout(&graph),
2390            [("primary", 0..1)],
2391            PartitionOwnership::new(false, false, ["embedding", "normalization"]).unwrap(),
2392            None,
2393            (),
2394            (),
2395            [
2396                OwnedParameterGroupSpec::new(
2397                    ParameterGroupOwner::static_role("embedding"),
2398                    parameter("first", "shared.weight"),
2399                ),
2400                OwnedParameterGroupSpec::new(
2401                    ParameterGroupOwner::static_role("normalization"),
2402                    parameter("second", "shared.weight"),
2403                ),
2404            ],
2405        )
2406        .unwrap_err();
2407        assert_eq!(
2408            error,
2409            ArchitecturePartitionError::DuplicateParameterTarget("shared.weight".into())
2410        );
2411    }
2412
2413    fn layered_partition(
2414        storage_state: Range<usize>,
2415        owns_input: bool,
2416    ) -> ArchitecturePartition<(), ()> {
2417        let graph = ExecutionGraph::chain(["decoder"]).unwrap();
2418        let layout = ExecutionUnitLayout::new(&graph, [4]).unwrap();
2419        ArchitecturePartition::new(
2420            graph,
2421            layout,
2422            [("decoder", 1..3)],
2423            PartitionOwnership::new(owns_input, false, std::iter::empty::<String>()).unwrap(),
2424            Some(
2425                PartitionState::new(state_layout(storage_state.len()), storage_state.start)
2426                    .unwrap(),
2427            ),
2428            (),
2429            (),
2430            std::iter::empty(),
2431        )
2432        .unwrap()
2433    }
2434
2435    #[test]
2436    fn layered_driver_rejects_storage_and_state_range_drift() {
2437        let partition = layered_partition(1..3, true);
2438        assert!(LayeredPartitionDriver::new(&partition, 0, 1..3).is_ok());
2439        assert_eq!(
2440            LayeredPartitionDriver::new(&partition, 0, 0..2).unwrap_err(),
2441            LayeredPartitionError::StorageRange {
2442                storage: 0..2,
2443                partition: 1..3,
2444            }
2445        );
2446
2447        let partition = layered_partition(0..2, true);
2448        assert_eq!(
2449            LayeredPartitionDriver::new(&partition, 0, 1..3).unwrap_err(),
2450            LayeredPartitionError::StateRange {
2451                state: 0..2,
2452                partition: 1..3,
2453            }
2454        );
2455    }
2456
2457    #[test]
2458    fn layered_driver_restricts_tokens_but_accepts_architecture_prepared_hidden() {
2459        let input_owner =
2460            LayeredPartitionDriver::new(&layered_partition(1..3, true), 0, 1..3).unwrap();
2461        assert!(matches!(
2462            input_owner.input(LayeredPartitionInput::<i32, NoAuxiliaryBoundary>::Tokens(
2463                &7
2464            )),
2465            Ok(LayeredPartitionInput::Tokens(7))
2466        ));
2467        assert!(matches!(
2468            input_owner.input(LayeredPartitionInput::Hidden {
2469                hidden: 7,
2470                auxiliary: NoAuxiliaryBoundary,
2471            }),
2472            Ok(LayeredPartitionInput::Hidden {
2473                hidden: 7,
2474                auxiliary: NoAuxiliaryBoundary,
2475            })
2476        ));
2477
2478        let hidden_owner =
2479            LayeredPartitionDriver::new(&layered_partition(1..3, false), 0, 1..3).unwrap();
2480        assert_eq!(
2481            hidden_owner
2482                .input(LayeredPartitionInput::<i32, NoAuxiliaryBoundary>::Tokens(
2483                    &7
2484                ))
2485                .unwrap_err(),
2486            LayeredPartitionError::TokensOnNonInputOwner
2487        );
2488        assert!(matches!(
2489            hidden_owner.input(LayeredPartitionInput::Hidden {
2490                hidden: 7,
2491                auxiliary: NoAuxiliaryBoundary,
2492            }),
2493            Ok(LayeredPartitionInput::Hidden {
2494                hidden: 7,
2495                auxiliary: NoAuxiliaryBoundary,
2496            })
2497        ));
2498    }
2499}