Skip to main content

eredu_runtime/
parallel.rs

1//! Backend-neutral semantic parameter sharding and rank-local layouts.
2//!
3//! Architectures describe physical checkpoint members in logical groups. An
4//! execution backend may then realize the resulting placement without knowing
5//! projection names, attention geometry, or other model-family semantics.
6
7use std::{
8    collections::{BTreeMap, BTreeSet},
9    ops::Range,
10};
11
12use eredu_checkpoint::LinearFormat;
13use eredu_nn::{LinearFormatSpec, ParameterMetadata, ParameterVisitor, Parameterized, Tensor};
14
15/// Architecture-neutral information for one rank-local parallel model.
16#[derive(Debug, Clone)]
17pub struct ParallelModelInfo<T> {
18    topology: T,
19    effective_model_type: String,
20    owned_tensors: Vec<String>,
21    local_parameter_bytes: u64,
22    global_parameter_bytes: u64,
23    pinned_device_parameter_bytes: u64,
24    maximum_device_parameter_bytes: u64,
25}
26
27impl<T> ParallelModelInfo<T> {
28    /// Creates a complete rank-local parallel model summary.
29    #[allow(clippy::too_many_arguments)]
30    pub fn new(
31        topology: T,
32        effective_model_type: impl Into<String>,
33        owned_tensors: Vec<String>,
34        local_parameter_bytes: u64,
35        global_parameter_bytes: u64,
36        pinned_device_parameter_bytes: u64,
37        maximum_device_parameter_bytes: u64,
38    ) -> Self {
39        Self {
40            topology,
41            effective_model_type: effective_model_type.into(),
42            owned_tensors,
43            local_parameter_bytes,
44            global_parameter_bytes,
45            pinned_device_parameter_bytes,
46            maximum_device_parameter_bytes,
47        }
48    }
49
50    /// Returns the backend's concrete topology value unchanged.
51    pub fn topology(&self) -> T
52    where
53        T: Clone,
54    {
55        self.topology.clone()
56    }
57
58    /// Returns the parsed implementation or nested text-model type.
59    pub fn effective_model_type(&self) -> &str {
60        &self.effective_model_type
61    }
62
63    /// Returns exact checkpoint targets owned or replicated by this rank.
64    pub fn owned_tensors(&self) -> &[String] {
65        &self.owned_tensors
66    }
67
68    /// Returns planned rank-local parameter bytes across static and execution units.
69    pub const fn local_parameter_bytes(&self) -> u64 {
70        self.local_parameter_bytes
71    }
72
73    /// Returns the unsharded model parameter bytes represented by this checkpoint.
74    pub const fn global_parameter_bytes(&self) -> u64 {
75        self.global_parameter_bytes
76    }
77
78    /// Returns rank-local parameter bytes permanently pinned on the execution device.
79    pub const fn pinned_device_parameter_bytes(&self) -> u64 {
80        self.pinned_device_parameter_bytes
81    }
82
83    /// Returns the maximum planned rank-local parameter footprint on device.
84    pub const fn maximum_device_parameter_bytes(&self) -> u64 {
85        self.maximum_device_parameter_bytes
86    }
87}
88
89/// Semantic role of a logical parameter group.
90#[derive(Debug, Clone, Copy, Eq, PartialEq)]
91pub enum ParameterRole {
92    /// Small or otherwise non-partitioned state.
93    Replicated,
94    /// Projection whose output features are rank-local.
95    ColumnProjection,
96    /// Projection whose input features are rank-local and whose output is reduced.
97    RowProjection,
98    /// Token embedding or output projection partitioned by vocabulary.
99    Vocabulary,
100    /// Query, key, or value heads.
101    AttentionHeads,
102    /// Dense feed-forward intermediate channels shared by input and output projections.
103    FeedForwardIntermediate,
104    /// Routed or shared expert intermediate channels.
105    ExpertIntermediate,
106    /// State-space, convolution, or recurrent channels.
107    Channels,
108    /// A fused tensor containing independently partitioned segments.
109    Segmented,
110}
111
112/// Logical sharding behavior for a parameterized affine projection.
113#[derive(Debug, Clone, Copy, Eq, PartialEq)]
114pub enum ProjectionSharding {
115    /// Keep every projection parameter complete on every rank.
116    Replicated,
117    /// Partition projection output features.
118    Column,
119    /// Partition projection input features and replicate output bias.
120    Row,
121}
122
123/// Rank-local selection rule for one physical checkpoint tensor.
124#[derive(Debug, Clone, Eq, PartialEq)]
125pub enum MemberSharding {
126    /// Materialize the complete member on every tensor-parallel rank.
127    Replicated,
128    /// Split an axis into equal contiguous shards.
129    Equal {
130        /// Source tensor axis to partition.
131        axis: usize,
132    },
133    /// Split an axis into balanced, potentially uneven contiguous ranges.
134    Balanced {
135        /// Source tensor axis to partition.
136        axis: usize,
137    },
138    /// Map the group's logical partition onto one physical tensor axis.
139    Partitioned {
140        /// Source tensor axis to partition.
141        axis: usize,
142    },
143    /// Map the same group-level logical range into each supplied source segment.
144    PartitionedSegments {
145        /// Source tensor axis containing the fused segments.
146        axis: usize,
147        /// Ordered, non-overlapping physical source ranges.
148        segments: Vec<Range<usize>>,
149    },
150    /// Partition each supplied source range independently.
151    Segmented {
152        /// Source tensor axis containing the fused segments.
153        axis: usize,
154        /// Ordered, non-overlapping source ranges.
155        segments: Vec<Range<usize>>,
156    },
157}
158
159/// One physical tensor belonging to a logical parameter group.
160#[derive(Debug, Clone, Eq, PartialEq)]
161pub struct ParameterMemberSpec {
162    target: String,
163    global_shape: Vec<usize>,
164    sharding: MemberSharding,
165}
166
167impl ParameterMemberSpec {
168    /// Creates a member with an exact pre-selection checkpoint shape.
169    pub fn new(
170        target: impl Into<String>,
171        global_shape: impl Into<Vec<usize>>,
172        sharding: MemberSharding,
173    ) -> Self {
174        Self {
175            target: target.into(),
176            global_shape: global_shape.into(),
177            sharding,
178        }
179    }
180
181    /// Returns the rewritten checkpoint target.
182    pub fn target(&self) -> &str {
183        &self.target
184    }
185
186    /// Returns the complete source shape.
187    pub fn global_shape(&self) -> &[usize] {
188        &self.global_shape
189    }
190
191    /// Returns the requested rank-local selection.
192    pub const fn sharding(&self) -> &MemberSharding {
193        &self.sharding
194    }
195}
196
197/// Atomic logical parameter and all of its physical checkpoint companions.
198#[derive(Debug, Clone, Eq, PartialEq)]
199pub struct ParameterGroupSpec {
200    logical_name: String,
201    role: ParameterRole,
202    partition_units: Option<usize>,
203    members: Vec<ParameterMemberSpec>,
204}
205
206impl ParameterGroupSpec {
207    /// Creates a non-empty logical group.
208    pub fn new(
209        logical_name: impl Into<String>,
210        role: ParameterRole,
211        members: impl IntoIterator<Item = ParameterMemberSpec>,
212    ) -> Result<Self, ParallelPlanError> {
213        Self::build(logical_name.into(), role, None, members)
214    }
215
216    /// Creates a group whose partitioned members share one logical domain.
217    pub fn partitioned(
218        logical_name: impl Into<String>,
219        role: ParameterRole,
220        units: usize,
221        members: impl IntoIterator<Item = ParameterMemberSpec>,
222    ) -> Result<Self, ParallelPlanError> {
223        if units == 0 {
224            return Err(ParallelPlanError::InvalidGroup(
225                "parallel logical partition must contain at least one unit".into(),
226            ));
227        }
228        Self::build(logical_name.into(), role, Some(units), members)
229    }
230
231    fn build(
232        logical_name: String,
233        role: ParameterRole,
234        partition_units: Option<usize>,
235        members: impl IntoIterator<Item = ParameterMemberSpec>,
236    ) -> Result<Self, ParallelPlanError> {
237        if logical_name.trim().is_empty() {
238            return Err(ParallelPlanError::InvalidGroup(
239                "parallel parameter logical name must not be empty".into(),
240            ));
241        }
242        let members = members.into_iter().collect::<Vec<_>>();
243        if members.is_empty() {
244            return Err(ParallelPlanError::InvalidGroup(format!(
245                "parallel parameter group {logical_name:?} must contain at least one tensor"
246            )));
247        }
248        let mut targets = BTreeSet::new();
249        let mut has_partitioned_member = false;
250        for member in &members {
251            if member.target.trim().is_empty() {
252                return Err(ParallelPlanError::InvalidGroup(format!(
253                    "parallel parameter group {logical_name:?} contains an empty tensor target"
254                )));
255            }
256            if !targets.insert(member.target.clone()) {
257                return Err(ParallelPlanError::InvalidGroup(format!(
258                    "parallel parameter group {logical_name:?} repeats tensor target {:?}",
259                    member.target
260                )));
261            }
262            has_partitioned_member |= matches!(
263                member.sharding,
264                MemberSharding::Partitioned { .. } | MemberSharding::PartitionedSegments { .. }
265            );
266        }
267        if has_partitioned_member != partition_units.is_some() {
268            return Err(ParallelPlanError::InvalidGroup(format!(
269                "parallel parameter group {logical_name:?} must declare exactly one group-level logical partition for its partitioned members"
270            )));
271        }
272        Ok(Self {
273            logical_name,
274            role,
275            partition_units,
276            members,
277        })
278    }
279
280    /// Returns the stable logical name.
281    pub fn logical_name(&self) -> &str {
282        &self.logical_name
283    }
284
285    /// Returns the semantic role.
286    pub const fn role(&self) -> ParameterRole {
287        self.role
288    }
289
290    /// Returns the shared logical-unit count, when the group is partitioned.
291    pub const fn partition_units(&self) -> Option<usize> {
292        self.partition_units
293    }
294
295    /// Returns physical checkpoint members.
296    pub fn members(&self) -> &[ParameterMemberSpec] {
297        &self.members
298    }
299}
300
301/// Describes every parameter in a neutral module as one logical group.
302pub fn module_parameter_group<T, M>(
303    logical_name: impl Into<String>,
304    role: ParameterRole,
305    module: &M,
306    mut sharding: impl FnMut(&ParameterMetadata, &[usize]) -> Result<MemberSharding, ParallelPlanError>,
307) -> Result<ParameterGroupSpec, ParallelPlanError>
308where
309    T: Tensor,
310    M: Parameterized<T>,
311{
312    struct Collector<'a, F> {
313        members: Vec<ParameterMemberSpec>,
314        sharding: &'a mut F,
315        error: Option<ParallelPlanError>,
316    }
317
318    impl<'a, 'tensor, T, F> ParameterVisitor<'tensor, T> for Collector<'a, F>
319    where
320        T: Tensor,
321        F: FnMut(&ParameterMetadata, &[usize]) -> Result<MemberSharding, ParallelPlanError>,
322    {
323        fn visit(&mut self, metadata: ParameterMetadata, value: &'tensor T) {
324            if self.error.is_some() {
325                return;
326            }
327            let shape = value
328                .shape()
329                .iter()
330                .map(|dimension| {
331                    usize::try_from(*dimension).map_err(|_| {
332                        ParallelPlanError::InvalidTensor(format!(
333                            "parameter {} has negative dimension {dimension}",
334                            metadata.id.as_str()
335                        ))
336                    })
337                })
338                .collect::<Result<Vec<_>, _>>();
339            let shape = match shape {
340                Ok(shape) => shape,
341                Err(error) => {
342                    self.error = Some(error);
343                    return;
344                }
345            };
346            match (self.sharding)(&metadata, &shape) {
347                Ok(sharding) => self.members.push(ParameterMemberSpec::new(
348                    metadata.id.as_str(),
349                    shape,
350                    sharding,
351                )),
352                Err(error) => self.error = Some(error),
353            }
354        }
355    }
356
357    let mut collector = Collector {
358        members: Vec::new(),
359        sharding: &mut sharding,
360        error: None,
361    };
362    module.visit_parameters(&mut collector);
363    if let Some(error) = collector.error {
364        return Err(error);
365    }
366    ParameterGroupSpec::new(logical_name, role, collector.members)
367}
368
369/// Describes every parameter in a neutral module as one shared logical partition.
370pub fn partitioned_module_parameter_group<T, M>(
371    logical_name: impl Into<String>,
372    role: ParameterRole,
373    preferred_units: usize,
374    module: &M,
375    mut sharding: impl FnMut(&ParameterMetadata, &[usize]) -> Result<MemberSharding, ParallelPlanError>,
376) -> Result<ParameterGroupSpec, ParallelPlanError>
377where
378    T: Tensor,
379    M: Parameterized<T>,
380{
381    if preferred_units == 0 {
382        return Err(ParallelPlanError::InvalidGroup(
383            "partitioned module group has zero preferred units".into(),
384        ));
385    }
386    struct Collector<'a, F> {
387        members: Vec<ParameterMemberSpec>,
388        sharding: &'a mut F,
389        error: Option<ParallelPlanError>,
390    }
391    impl<'a, 'tensor, T, F> ParameterVisitor<'tensor, T> for Collector<'a, F>
392    where
393        T: Tensor,
394        F: FnMut(&ParameterMetadata, &[usize]) -> Result<MemberSharding, ParallelPlanError>,
395    {
396        fn visit(&mut self, metadata: ParameterMetadata, value: &'tensor T) {
397            if self.error.is_some() {
398                return;
399            }
400            let shape = value
401                .shape()
402                .iter()
403                .map(|dimension| {
404                    usize::try_from(*dimension).map_err(|_| {
405                        ParallelPlanError::InvalidTensor(format!(
406                            "parameter {} has negative dimension {dimension}",
407                            metadata.id.as_str()
408                        ))
409                    })
410                })
411                .collect::<Result<Vec<_>, _>>();
412            match shape.and_then(|shape| {
413                (self.sharding)(&metadata, &shape)
414                    .map(|sharding| ParameterMemberSpec::new(metadata.id.as_str(), shape, sharding))
415            }) {
416                Ok(member) => self.members.push(member),
417                Err(error) => self.error = Some(error),
418            }
419        }
420    }
421    let mut collector = Collector {
422        members: Vec::new(),
423        sharding: &mut sharding,
424        error: None,
425    };
426    module.visit_parameters(&mut collector);
427    if let Some(error) = collector.error {
428        return Err(error);
429    }
430    partitioned_group_with_preferred_units(logical_name, role, preferred_units, collector.members)
431}
432
433/// Describes one affine projection and all encoding companions.
434pub fn projection_parameter_group<T, M>(
435    logical_name: impl Into<String>,
436    role: ParameterRole,
437    module: &M,
438    placement: ProjectionSharding,
439) -> Result<ParameterGroupSpec, ParallelPlanError>
440where
441    T: Tensor,
442    M: Parameterized<T>,
443{
444    module_parameter_group(
445        logical_name,
446        role,
447        module,
448        |metadata, shape| match placement {
449            ProjectionSharding::Replicated => Ok(MemberSharding::Replicated),
450            ProjectionSharding::Column if shape.is_empty() => {
451                Err(ParallelPlanError::InvalidTensor(format!(
452                    "column projection parameter {} is scalar",
453                    metadata.id.as_str()
454                )))
455            }
456            ProjectionSharding::Column => Ok(MemberSharding::Equal { axis: 0 }),
457            ProjectionSharding::Row if shape.len() >= 2 => Ok(MemberSharding::Equal { axis: 1 }),
458            ProjectionSharding::Row => Ok(MemberSharding::Replicated),
459        },
460    )
461}
462
463/// Describes projections that consume one shared logical partition.
464pub fn partitioned_projection_group<T, M>(
465    logical_name: impl Into<String>,
466    role: ParameterRole,
467    projections: &[(&M, ProjectionSharding)],
468    preferred_units: usize,
469) -> Result<ParameterGroupSpec, ParallelPlanError>
470where
471    T: Tensor,
472    M: Parameterized<T>,
473{
474    if preferred_units == 0 {
475        return Err(ParallelPlanError::InvalidGroup(
476            "partitioned projection group has zero preferred units".into(),
477        ));
478    }
479    let mut members = Vec::new();
480    for (module, placement) in projections {
481        let group = projection_parameter_group::<T, M>("projection", role, *module, *placement)?;
482        for member in group.members {
483            let sharding = match (placement, member.global_shape.len()) {
484                (ProjectionSharding::Replicated, _) | (ProjectionSharding::Row, 0 | 1) => {
485                    MemberSharding::Replicated
486                }
487                (ProjectionSharding::Column, 0) => unreachable!("validated above"),
488                (ProjectionSharding::Column, _) => MemberSharding::Partitioned { axis: 0 },
489                (ProjectionSharding::Row, _) => MemberSharding::Partitioned { axis: 1 },
490            };
491            members.push(ParameterMemberSpec::new(
492                member.target,
493                member.global_shape,
494                sharding,
495            ));
496        }
497    }
498    partitioned_group_with_preferred_units(logical_name, role, preferred_units, members)
499}
500
501/// Describes a component-major fused column projection and its row-parallel
502/// output as one shared logical partition.
503///
504/// The same ordered segment selection is attached to the fused weight and all
505/// encoding companions exposed by the module. The row projection consumes the
506/// corresponding local hidden partition and is reduced once by the backend.
507pub fn segmented_projection_group<T, M>(
508    logical_name: impl Into<String>,
509    role: ParameterRole,
510    fused: &M,
511    row: &M,
512    segments: Vec<Range<usize>>,
513    preferred_units: usize,
514) -> Result<ParameterGroupSpec, ParallelPlanError>
515where
516    T: Tensor,
517    M: Parameterized<T>,
518{
519    if preferred_units == 0 || segments.is_empty() {
520        return Err(ParallelPlanError::InvalidGroup(
521            "segmented projection requires positive logical units and at least one segment".into(),
522        ));
523    }
524    let mut previous_end = 0usize;
525    for segment in &segments {
526        if segment.start != previous_end || segment.start >= segment.end {
527            return Err(ParallelPlanError::InvalidGroup(format!(
528                "segmented projection ranges must be positive, contiguous, and ordered, got {segments:?}"
529            )));
530        }
531        previous_end = segment.end;
532    }
533
534    let fused_group =
535        projection_parameter_group::<T, M>("fused", role, fused, ProjectionSharding::Column)?;
536    let row_group = projection_parameter_group::<T, M>("row", role, row, ProjectionSharding::Row)?;
537    assemble_segmented_projection_group(
538        logical_name,
539        role,
540        fused_group,
541        row_group,
542        segments,
543        preferred_units,
544        previous_end,
545    )
546}
547
548#[allow(clippy::too_many_arguments)]
549fn assemble_segmented_projection_group(
550    logical_name: impl Into<String>,
551    role: ParameterRole,
552    fused_group: ParameterGroupSpec,
553    row_group: ParameterGroupSpec,
554    segments: Vec<Range<usize>>,
555    units: usize,
556    expected_fused_width: usize,
557) -> Result<ParameterGroupSpec, ParallelPlanError> {
558    let mut members = Vec::new();
559    for member in fused_group.members {
560        let dimension = member.global_shape.first().copied().ok_or_else(|| {
561            ParallelPlanError::InvalidTensor(format!(
562                "segmented projection parameter {} is scalar",
563                member.target
564            ))
565        })?;
566        if dimension != expected_fused_width {
567            return Err(ParallelPlanError::InvalidTensor(format!(
568                "segmented projection parameter {} has output dimension {dimension}, expected {expected_fused_width}",
569                member.target
570            )));
571        }
572        members.push(ParameterMemberSpec::new(
573            member.target,
574            member.global_shape,
575            MemberSharding::PartitionedSegments {
576                axis: 0,
577                segments: segments.clone(),
578            },
579        ));
580    }
581    for member in row_group.members {
582        let sharding = if member.global_shape.len() >= 2 {
583            MemberSharding::Partitioned { axis: 1 }
584        } else {
585            MemberSharding::Replicated
586        };
587        members.push(ParameterMemberSpec::new(
588            member.target,
589            member.global_shape,
590            sharding,
591        ));
592    }
593    partitioned_group_with_preferred_units(logical_name, role, units, members)
594}
595
596/// Returns the finest legal logical-unit count for an aligned partition.
597pub fn aligned_partition_units(
598    name: &str,
599    semantic_units: usize,
600    elements_per_unit: usize,
601    required_alignment: usize,
602) -> Result<usize, ParallelPlanError> {
603    if semantic_units == 0 || elements_per_unit == 0 || required_alignment == 0 {
604        return Err(ParallelPlanError::InvalidGroup(format!(
605            "{name} aligned partition dimensions must be positive, got units={semantic_units}, width={elements_per_unit}, alignment={required_alignment}"
606        )));
607    }
608    let units_per_partition =
609        required_alignment / greatest_common_divisor(elements_per_unit, required_alignment);
610    if !semantic_units.is_multiple_of(units_per_partition) {
611        return Err(ParallelPlanError::InvalidGroup(format!(
612            "{name} has {semantic_units} semantic units of width {elements_per_unit}, which cannot form complete alignment-{required_alignment} partitions"
613        )));
614    }
615    Ok(semantic_units / units_per_partition)
616}
617
618/// Rewrites semantic dense matrix declarations into their authoritative
619/// physical checkpoint representation and publishes every required companion
620/// in the same atomic parameter group.
621pub fn expand_linear_format_parameter_groups(
622    groups: Vec<ParameterGroupSpec>,
623    declaration: impl Fn(&ParameterMemberSpec) -> Result<Option<LinearFormatSpec>, ParallelPlanError>,
624) -> Result<Vec<ParameterGroupSpec>, ParallelPlanError> {
625    groups
626        .into_iter()
627        .map(|group| {
628            let mut members = Vec::new();
629            for source in group.members() {
630                members.extend(match declaration(source)? {
631                    Some(declaration) => expand_linear_format_member(source, &declaration)?,
632                    None => vec![source.clone()],
633                });
634            }
635            match group.partition_units() {
636                Some(units) => partitioned_group_with_preferred_units(
637                    group.logical_name(),
638                    group.role(),
639                    units,
640                    members,
641                ),
642                None => ParameterGroupSpec::new(group.logical_name(), group.role(), members),
643            }
644        })
645        .collect()
646}
647
648fn partitioned_group_with_preferred_units(
649    logical_name: impl Into<String>,
650    role: ParameterRole,
651    preferred_units: usize,
652    members: Vec<ParameterMemberSpec>,
653) -> Result<ParameterGroupSpec, ParallelPlanError> {
654    let mut units = preferred_units;
655    for member in &members {
656        match member.sharding() {
657            MemberSharding::Partitioned { axis } => {
658                let dimension = member.global_shape().get(*axis).ok_or_else(|| {
659                    ParallelPlanError::InvalidTensor(format!(
660                        "partitioned parameter {} has no axis {axis}",
661                        member.target()
662                    ))
663                })?;
664                units = greatest_common_divisor(units, *dimension);
665            }
666            MemberSharding::PartitionedSegments { axis, segments }
667            | MemberSharding::Segmented { axis, segments } => {
668                if member.global_shape().get(*axis).is_none() {
669                    return Err(ParallelPlanError::InvalidTensor(format!(
670                        "segmented parameter {} has no axis {axis}",
671                        member.target()
672                    )));
673                }
674                for segment in segments {
675                    units = greatest_common_divisor(units, segment.len());
676                }
677            }
678            MemberSharding::Replicated
679            | MemberSharding::Equal { .. }
680            | MemberSharding::Balanced { .. } => {}
681        }
682    }
683    ParameterGroupSpec::partitioned(logical_name, role, units, members)
684}
685
686fn remap_linear_segments(
687    sharding: &MemberSharding,
688    axis: usize,
689    divisor: usize,
690    name: &str,
691) -> Result<MemberSharding, ParallelPlanError> {
692    let remap = |segments: &[Range<usize>]| {
693        segments
694            .iter()
695            .map(|segment| {
696                if !segment.start.is_multiple_of(divisor) || !segment.end.is_multiple_of(divisor) {
697                    return Err(ParallelPlanError::InvalidTensor(format!(
698                        "packed companion {name} segment {segment:?} is not aligned to {divisor}"
699                    )));
700                }
701                Ok(segment.start / divisor..segment.end / divisor)
702            })
703            .collect::<Result<Vec<_>, _>>()
704    };
705    match sharding {
706        MemberSharding::PartitionedSegments {
707            axis: selected,
708            segments,
709        } if *selected == axis => Ok(MemberSharding::PartitionedSegments {
710            axis: *selected,
711            segments: remap(segments)?,
712        }),
713        MemberSharding::Segmented {
714            axis: selected,
715            segments,
716        } if *selected == axis => Ok(MemberSharding::Segmented {
717            axis: *selected,
718            segments: remap(segments)?,
719        }),
720        other => Ok(other.clone()),
721    }
722}
723
724fn expand_linear_format_member(
725    source: &ParameterMemberSpec,
726    declaration: &LinearFormatSpec,
727) -> Result<Vec<ParameterMemberSpec>, ParallelPlanError> {
728    let name = source.target();
729    let shape = source.global_shape();
730    let format = declaration.encoding();
731    if format == LinearFormat::Dense {
732        return if declaration.scale().is_none() && declaration.affine_bias().is_none() {
733            Ok(vec![source.clone()])
734        } else {
735            Err(ParallelPlanError::InvalidGroup(format!(
736                "dense linear parameter {name} declares physical companions"
737            )))
738        };
739    }
740    if shape.len() < 2 {
741        return Err(ParallelPlanError::InvalidTensor(format!(
742            "encoded linear parameter {name} must have at least two dimensions"
743        )));
744    }
745    let row_axis = shape.len() - 2;
746    let column_axis = shape.len() - 1;
747    let invalid = |detail: String| ParallelPlanError::InvalidTensor(detail);
748    match format {
749        LinearFormat::Dense => unreachable!(),
750        LinearFormat::E4M3BlockFp8(fp8) => {
751            let Some(scale) = declaration.scale() else {
752                return Err(ParallelPlanError::InvalidGroup(format!(
753                    "block-FP8 linear parameter {name} must declare exactly one scale companion"
754                )));
755            };
756            if declaration.affine_bias().is_some() {
757                return Err(ParallelPlanError::InvalidGroup(format!(
758                    "block-FP8 linear parameter {name} must not declare an affine-bias companion"
759                )));
760            }
761            fp8.validate().map_err(|error| invalid(error.to_string()))?;
762            let rows = usize::try_from(fp8.block_rows)
763                .map_err(|_| invalid(format!("invalid block rows for {name}")))?;
764            let columns = usize::try_from(fp8.block_columns)
765                .map_err(|_| invalid(format!("invalid block columns for {name}")))?;
766            let mut scale_shape = shape.to_vec();
767            scale_shape[row_axis] = scale_shape[row_axis].div_ceil(rows);
768            scale_shape[column_axis] = scale_shape[column_axis].div_ceil(columns);
769            let scale_sharding = remap_linear_segments(source.sharding(), row_axis, rows, name)
770                .and_then(|value| remap_linear_segments(&value, column_axis, columns, name))?;
771            Ok(vec![
772                source.clone(),
773                ParameterMemberSpec::new(scale.id.as_str(), scale_shape, scale_sharding),
774            ])
775        }
776        LinearFormat::GgufIQuant { ggml_type, .. } => {
777            if declaration.scale().is_some() || declaration.affine_bias().is_some() {
778                return Err(ParallelPlanError::InvalidGroup(format!(
779                    "GGUF linear parameter {name} must not declare companion tensors"
780                )));
781            }
782            let (block_values, block_bytes) = ggml_type
783                .block_and_bytes()
784                .map_err(|error| invalid(error.to_string()))?;
785            let block_values = usize::try_from(block_values)
786                .map_err(|_| invalid(format!("GGUF block width for {name} exceeds usize")))?;
787            let block_bytes = usize::try_from(block_bytes)
788                .map_err(|_| invalid(format!("GGUF block bytes for {name} exceeds usize")))?;
789            let input = shape[column_axis];
790            if !input.is_multiple_of(block_values) {
791                return Err(invalid(format!(
792                    "GGUF matrix {name} input {input} is not aligned to block {block_values}"
793                )));
794            }
795            let mut packed = shape.to_vec();
796            packed[column_axis] = input / block_values * block_bytes;
797            Ok(vec![ParameterMemberSpec::new(
798                name,
799                packed,
800                remap_linear_segments(source.sharding(), column_axis, block_values, name)?,
801            )])
802        }
803        LinearFormat::Affine(_) | LinearFormat::MxFp4 => {
804            let quantization = format.weight_quantization().expect("packed format");
805            let Some(scale) = declaration.scale() else {
806                return Err(ParallelPlanError::InvalidGroup(format!(
807                    "packed linear parameter {name} must declare a scale companion"
808                )));
809            };
810            let bias = declaration.affine_bias();
811            if quantization.has_biases() != bias.is_some() {
812                return Err(ParallelPlanError::InvalidGroup(format!(
813                    "packed linear parameter {name} declares companions inconsistent with its format"
814                )));
815            }
816            let bits = usize::try_from(quantization.bits())
817                .map_err(|_| invalid(format!("packed bit width for {name} exceeds usize")))?;
818            let group = usize::try_from(quantization.group_size())
819                .map_err(|_| invalid(format!("packed group width for {name} exceeds usize")))?;
820            let input = shape[column_axis];
821            let packed_bits = input
822                .checked_mul(bits)
823                .ok_or_else(|| invalid(format!("packed matrix {name} overflows")))?;
824            if group == 0 || !input.is_multiple_of(group) || !packed_bits.is_multiple_of(32) {
825                return Err(invalid(format!(
826                    "packed matrix {name} input {input} is incompatible with group {group} and {bits} bits"
827                )));
828            }
829            let mut packed = shape.to_vec();
830            packed[column_axis] = packed_bits / 32;
831            let mut companion = shape.to_vec();
832            companion[column_axis] = input / group;
833            let mut members = vec![ParameterMemberSpec::new(
834                name,
835                packed,
836                remap_linear_segments(source.sharding(), column_axis, 32 / bits, name)?,
837            )];
838            let companion_sharding =
839                remap_linear_segments(source.sharding(), column_axis, group, name)?;
840            members.push(ParameterMemberSpec::new(
841                scale.id.as_str(),
842                companion.clone(),
843                companion_sharding.clone(),
844            ));
845            if let Some(bias) = bias {
846                members.push(ParameterMemberSpec::new(
847                    bias.id.as_str(),
848                    companion,
849                    companion_sharding,
850                ));
851            }
852            Ok(members)
853        }
854    }
855}
856
857const fn greatest_common_divisor(mut left: usize, mut right: usize) -> usize {
858    while right != 0 {
859        let remainder = left % right;
860        left = right;
861        right = remainder;
862    }
863    left
864}
865
866/// Behavior when a requested shard is not legal for the current TP size.
867#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
868pub enum ShardingPolicy {
869    /// Reject the complete plan with a precise shape/alignment error.
870    #[default]
871    Require,
872    /// Replicate the complete logical parameter group.
873    ReplicateUnsupported,
874}
875
876/// Backend-neutral placement decision for one physical tensor.
877#[derive(Debug, Clone, Eq, PartialEq)]
878pub enum TensorPlacement {
879    /// Materialize the complete tensor on every rank.
880    Replicated,
881    /// Materialize the complete tensor on this rank.
882    Local,
883    /// Intentionally omit this tensor on this rank.
884    Omit,
885    /// Materialize the complete tensor only on one global rank.
886    Rank {
887        /// Owning global rank.
888        rank: usize,
889    },
890    /// Materialize an equal contiguous source-tensor slice.
891    Shard {
892        /// Source tensor axis being sharded.
893        axis: usize,
894        /// Shard index.
895        index: usize,
896        /// Total shard count.
897        parts: usize,
898    },
899    /// Materialize an explicit contiguous source-tensor range.
900    Range {
901        /// Source tensor axis being sliced.
902        axis: usize,
903        /// Inclusive element offset on `axis`.
904        start: usize,
905        /// Exclusive element offset on `axis`.
906        end: usize,
907    },
908    /// Materialize selected source-tensor indices in the supplied order.
909    Indices {
910        /// Source tensor axis being selected.
911        axis: usize,
912        /// Distinct source indices in local output order.
913        indices: Vec<usize>,
914    },
915}
916
917/// Rank-local shape and placement for one planned physical tensor.
918#[derive(Debug, Clone, Eq, PartialEq)]
919pub struct LocalTensorLayout<P = TensorPlacement> {
920    logical_name: String,
921    role: ParameterRole,
922    global_shape: Vec<usize>,
923    local_shape: Vec<usize>,
924    placement: P,
925    logical_units: Option<usize>,
926    logical_range: Option<Range<usize>>,
927    fell_back_to_replication: bool,
928}
929
930impl<P> LocalTensorLayout<P> {
931    /// Creates one validated-planner output entry.
932    #[allow(clippy::too_many_arguments)]
933    pub fn new(
934        logical_name: impl Into<String>,
935        role: ParameterRole,
936        global_shape: Vec<usize>,
937        local_shape: Vec<usize>,
938        placement: P,
939        logical_units: Option<usize>,
940        logical_range: Option<Range<usize>>,
941        fell_back_to_replication: bool,
942    ) -> Self {
943        Self {
944            logical_name: logical_name.into(),
945            role,
946            global_shape,
947            local_shape,
948            placement,
949            logical_units,
950            logical_range,
951            fell_back_to_replication,
952        }
953    }
954
955    /// Returns the logical parameter group name.
956    pub fn logical_name(&self) -> &str {
957        &self.logical_name
958    }
959
960    /// Returns the semantic parameter role.
961    pub const fn role(&self) -> ParameterRole {
962        self.role
963    }
964
965    /// Returns the checkpoint-global shape.
966    pub fn global_shape(&self) -> &[usize] {
967        &self.global_shape
968    }
969
970    /// Returns the shape materialized on this rank.
971    pub fn local_shape(&self) -> &[usize] {
972        &self.local_shape
973    }
974
975    /// Returns the backend-realized placement.
976    pub const fn placement(&self) -> &P {
977        &self.placement
978    }
979
980    /// Returns the rank-local range in the parameter group's semantic domain.
981    pub fn logical_range(&self) -> Option<&Range<usize>> {
982        self.logical_range.as_ref()
983    }
984
985    /// Returns the size of the complete semantic partition domain.
986    pub const fn logical_units(&self) -> Option<usize> {
987        self.logical_units
988    }
989
990    /// Returns whether permissive planning replicated an unsupported shard.
991    pub const fn fell_back_to_replication(&self) -> bool {
992        self.fell_back_to_replication
993    }
994}
995
996/// Complete rank-local model geometry produced alongside checkpoint placement.
997#[derive(Debug, Clone)]
998pub struct LocalModelLayout<P = TensorPlacement> {
999    tensors: BTreeMap<String, LocalTensorLayout<P>>,
1000}
1001
1002impl<P> Default for LocalModelLayout<P> {
1003    fn default() -> Self {
1004        Self {
1005            tensors: BTreeMap::new(),
1006        }
1007    }
1008}
1009
1010impl<P> LocalModelLayout<P> {
1011    /// Returns whether a physical target has already been planned.
1012    pub fn contains(&self, target: &str) -> bool {
1013        self.tensors.contains_key(target)
1014    }
1015
1016    /// Inserts one planner-produced physical layout.
1017    pub fn insert(&mut self, target: String, layout: LocalTensorLayout<P>) {
1018        self.tensors.insert(target, layout);
1019    }
1020
1021    /// Returns one physical tensor layout by rewritten target name.
1022    pub fn tensor(&self, target: &str) -> Option<&LocalTensorLayout<P>> {
1023        self.tensors.get(target)
1024    }
1025
1026    /// Iterates physical layouts in deterministic target-name order.
1027    pub fn tensors(&self) -> impl Iterator<Item = (&str, &LocalTensorLayout<P>)> {
1028        self.tensors
1029            .iter()
1030            .map(|(target, layout)| (target.as_str(), layout))
1031    }
1032
1033    /// Returns the number of planned physical tensors.
1034    pub fn len(&self) -> usize {
1035        self.tensors.len()
1036    }
1037
1038    /// Returns whether no physical tensors were planned.
1039    pub fn is_empty(&self) -> bool {
1040        self.tensors.is_empty()
1041    }
1042}
1043
1044/// Invalid architecture-declared parallel semantics.
1045#[derive(Debug, Clone, Eq, PartialEq, thiserror::Error)]
1046pub enum ParallelPlanError {
1047    /// A logical group is empty, ambiguous, or internally inconsistent.
1048    #[error("{0}")]
1049    InvalidGroup(String),
1050    /// A backend-native parameter exposed invalid logical geometry.
1051    #[error("{0}")]
1052    InvalidTensor(String),
1053}
1054
1055#[cfg(test)]
1056mod tests {
1057    use eredu_checkpoint::{BlockFp8Format, BlockFp8ScaleEncoding};
1058
1059    use super::*;
1060
1061    #[test]
1062    fn groups_reject_duplicate_physical_targets() {
1063        let error = ParameterGroupSpec::new(
1064            "attention",
1065            ParameterRole::AttentionHeads,
1066            [
1067                ParameterMemberSpec::new("q.weight", [8, 8], MemberSharding::Replicated),
1068                ParameterMemberSpec::new("q.weight", [8, 8], MemberSharding::Replicated),
1069            ],
1070        )
1071        .unwrap_err();
1072        assert!(error.to_string().contains("repeats tensor target"));
1073    }
1074
1075    #[test]
1076    fn group_partition_contract_is_explicit() {
1077        assert!(ParameterGroupSpec::new(
1078            "query",
1079            ParameterRole::AttentionHeads,
1080            [ParameterMemberSpec::new(
1081                "q.weight",
1082                [8, 8],
1083                MemberSharding::Partitioned { axis: 0 },
1084            )],
1085        )
1086        .is_err());
1087        assert!(ParameterGroupSpec::partitioned(
1088            "query",
1089            ParameterRole::AttentionHeads,
1090            4,
1091            [ParameterMemberSpec::new(
1092                "q.weight",
1093                [8, 8],
1094                MemberSharding::Partitioned { axis: 0 },
1095            )],
1096        )
1097        .is_ok());
1098    }
1099
1100    #[test]
1101    fn preferred_partition_units_follow_every_physical_companion() {
1102        let group = partitioned_group_with_preferred_units(
1103            "experts.intermediate",
1104            ParameterRole::ExpertIntermediate,
1105            64,
1106            vec![
1107                ParameterMemberSpec::new(
1108                    "experts.up_proj",
1109                    [4, 64, 16],
1110                    MemberSharding::Partitioned { axis: 1 },
1111                ),
1112                ParameterMemberSpec::new(
1113                    "experts.down_proj",
1114                    [4, 16, 8],
1115                    MemberSharding::Partitioned { axis: 2 },
1116                ),
1117                ParameterMemberSpec::new(
1118                    "experts.down_proj_scales",
1119                    [4, 16, 2],
1120                    MemberSharding::Partitioned { axis: 2 },
1121                ),
1122            ],
1123        )
1124        .unwrap();
1125
1126        assert_eq!(group.partition_units(), Some(2));
1127    }
1128
1129    #[test]
1130    fn fp8_expansion_uses_architecture_declared_companion_identity() {
1131        let groups = vec![ParameterGroupSpec::partitioned(
1132            "query",
1133            ParameterRole::AttentionHeads,
1134            8,
1135            [ParameterMemberSpec::new(
1136                "opaque_matrix",
1137                [256, 256],
1138                MemberSharding::Partitioned { axis: 0 },
1139            )],
1140        )
1141        .unwrap()];
1142        let format = LinearFormat::E4M3BlockFp8(
1143            BlockFp8Format::new(128, 128, BlockFp8ScaleEncoding::Ue8m0).unwrap(),
1144        );
1145
1146        let expanded = expand_linear_format_parameter_groups(groups, |_| {
1147            Ok(Some(
1148                LinearFormatSpec::scaled(
1149                    format,
1150                    eredu_nn::ParameterSpec::trainable("opaque_scale").unwrap(),
1151                )
1152                .unwrap(),
1153            ))
1154        })
1155        .unwrap();
1156        assert_eq!(expanded.len(), 1);
1157        assert_eq!(expanded[0].partition_units(), Some(2));
1158        assert_eq!(expanded[0].members().len(), 2);
1159        assert_eq!(
1160            expanded[0]
1161                .members()
1162                .iter()
1163                .map(ParameterMemberSpec::target)
1164                .collect::<Vec<_>>(),
1165            ["opaque_matrix", "opaque_scale"]
1166        );
1167        assert_eq!(expanded[0].members()[1].global_shape(), [2, 2]);
1168        assert_eq!(
1169            expanded[0].members()[1].sharding(),
1170            &MemberSharding::Partitioned { axis: 0 }
1171        );
1172    }
1173
1174    #[test]
1175    fn segmented_projection_applies_identical_ranges_to_every_fused_companion() {
1176        let fused = ParameterGroupSpec::new(
1177            "fused",
1178            ParameterRole::FeedForwardIntermediate,
1179            [
1180                ParameterMemberSpec::new(
1181                    "gate_up.weight",
1182                    [12, 8],
1183                    MemberSharding::Equal { axis: 0 },
1184                ),
1185                ParameterMemberSpec::new(
1186                    "gate_up.scales",
1187                    [12, 2],
1188                    MemberSharding::Equal { axis: 0 },
1189                ),
1190                ParameterMemberSpec::new(
1191                    "gate_up.biases",
1192                    [12, 2],
1193                    MemberSharding::Equal { axis: 0 },
1194                ),
1195            ],
1196        )
1197        .unwrap();
1198        let row = ParameterGroupSpec::new(
1199            "row",
1200            ParameterRole::FeedForwardIntermediate,
1201            [
1202                ParameterMemberSpec::new("down.weight", [8, 6], MemberSharding::Equal { axis: 1 }),
1203                ParameterMemberSpec::new("down.scales", [8, 2], MemberSharding::Equal { axis: 1 }),
1204                ParameterMemberSpec::new("down.bias", [8], MemberSharding::Replicated),
1205            ],
1206        )
1207        .unwrap();
1208        let segments = vec![0..4, 4..8, 8..12];
1209        let group = assemble_segmented_projection_group(
1210            "mlp.projections",
1211            ParameterRole::FeedForwardIntermediate,
1212            fused,
1213            row,
1214            segments.clone(),
1215            2,
1216            12,
1217        )
1218        .unwrap();
1219        assert_eq!(group.partition_units(), Some(2));
1220        for member in &group.members()[..3] {
1221            assert_eq!(
1222                member.sharding(),
1223                &MemberSharding::PartitionedSegments {
1224                    axis: 0,
1225                    segments: segments.clone(),
1226                }
1227            );
1228        }
1229        assert_eq!(
1230            group.members()[3].sharding(),
1231            &MemberSharding::Partitioned { axis: 1 }
1232        );
1233        assert_eq!(
1234            group.members()[4].sharding(),
1235            &MemberSharding::Partitioned { axis: 1 }
1236        );
1237        assert_eq!(group.members()[5].sharding(), &MemberSharding::Replicated);
1238    }
1239
1240    #[test]
1241    fn segmented_projection_rejects_one_misaligned_companion_atomically() {
1242        let fused = ParameterGroupSpec::new(
1243            "fused",
1244            ParameterRole::AttentionHeads,
1245            [
1246                ParameterMemberSpec::new("qkv.weight", [12, 8], MemberSharding::Equal { axis: 0 }),
1247                ParameterMemberSpec::new("qkv.scales", [11, 2], MemberSharding::Equal { axis: 0 }),
1248            ],
1249        )
1250        .unwrap();
1251        let row = ParameterGroupSpec::new(
1252            "row",
1253            ParameterRole::AttentionHeads,
1254            [ParameterMemberSpec::new(
1255                "output.weight",
1256                [8, 4],
1257                MemberSharding::Equal { axis: 1 },
1258            )],
1259        )
1260        .unwrap();
1261        assert!(matches!(
1262            assemble_segmented_projection_group(
1263                "attention.projections",
1264                ParameterRole::AttentionHeads,
1265                fused,
1266                row,
1267                vec![0..4, 4..8, 8..12],
1268                2,
1269                12,
1270            ),
1271            Err(ParallelPlanError::InvalidTensor(_))
1272        ));
1273    }
1274
1275    #[test]
1276    fn parallel_model_info_preserves_opaque_topology_and_accounting() {
1277        let info = ParallelModelInfo::new(
1278            (2usize, 1usize),
1279            "generic",
1280            vec!["layer.weight".into()],
1281            10,
1282            20,
1283            4,
1284            8,
1285        );
1286        assert_eq!(info.topology(), (2, 1));
1287        assert_eq!(info.effective_model_type(), "generic");
1288        assert_eq!(info.owned_tensors(), ["layer.weight"]);
1289        assert_eq!(info.local_parameter_bytes(), 10);
1290        assert_eq!(info.global_parameter_bytes(), 20);
1291        assert_eq!(info.pinned_device_parameter_bytes(), 4);
1292        assert_eq!(info.maximum_device_parameter_bytes(), 8);
1293    }
1294}